Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add team role management #7

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,12 @@ public class Team {
@ManyToMany(mappedBy = "teams")
@EqualsAndHashCode.Exclude
private Set<User> users = new HashSet<>();

@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
@EqualsAndHashCode.Exclude
private Set<UserTeam> teamMembers = new HashSet<>();

@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
@EqualsAndHashCode.Exclude
private Set<UserTeam> userTeams = new HashSet<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.flexwork.modules.usermanagement.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "fw_team_role")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TeamRole implements Serializable {

@Id
@Column(name = "name", nullable = false, length = 50)
private String name;

@Column(name = "description", columnDefinition = "TEXT")
private String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
})
private Set<Authority> authorities = new HashSet<>();

@EqualsAndHashCode.Exclude
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<UserTeam> userTeams = new HashSet<>();

public LocalDateTime getLastLoginTime() {
if (lastLoginTime == null) return null;
ZoneId userZone =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.flexwork.modules.usermanagement.domain;

import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "fw_user_team")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserTeam {
@EmbeddedId private UserTeamId id;

@ManyToOne
@MapsId("userId")
@JoinColumn(name = "user_id", nullable = false)
private User user;

@ManyToOne
@MapsId("teamId")
@JoinColumn(name = "team_id", nullable = false)
private Team team;

@ManyToOne
@JoinColumn(
name = "role_name",
referencedColumnName = "name",
nullable = false,
insertable = false,
updatable = false)
private TeamRole role;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.flexwork.modules.usermanagement.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Embeddable
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserTeamId implements Serializable {

@Column(name = "user_id", nullable = false)
private Long userId;

@Column(name = "team_id", nullable = false)
private Long teamId;

@Column(name = "role_name", length = 50, nullable = false)
private String roleName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.flexwork.modules.usermanagement.domain.Team;
import io.flexwork.modules.usermanagement.domain.User;
import io.flexwork.modules.usermanagement.service.dto.TeamDTO;
import io.flexwork.modules.usermanagement.service.dto.UserWithTeamRoleDTO;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -42,4 +43,25 @@ List<User> findUsersNotInTeam(
@Param("searchTerm") String searchTerm,
@Param("teamId") Long teamId,
Pageable pageable);

@Query(
"SELECT new io.flexwork.modules.usermanagement.service.dto.UserWithTeamRoleDTO(u.id, u.email, u.firstName, u.lastName, u.timezone, u.imageUrl, u.title, ut.team.id, ut.role.name) "
+ "FROM User u JOIN u.userTeams ut WHERE ut.team.id = :teamId")
Page<UserWithTeamRoleDTO> findUsersByTeamId(@Param("teamId") Long teamId, Pageable pageable);

/**
* Return the team role of user, return default value is 'Guest'
*
* @param userId
* @param teamId
* @return
*/
@Query(
"""
SELECT COALESCE(ut.role.name, 'Guest')
FROM UserTeam ut
WHERE ut.user.id = :userId
AND ut.team.id = :teamId
""")
String findUserRoleInTeam(@Param("userId") Long userId, @Param("teamId") Long teamId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.flexwork.modules.usermanagement.repository;

import io.flexwork.modules.usermanagement.domain.TeamRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TeamRoleRepository extends JpaRepository<TeamRole, String> {}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefor
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email);

@EntityGraph(attributePaths = {"manager"})
Optional<User> findOneWithManagerById(Long id);

@EntityGraph(attributePaths = "authorities")
Optional<User> findOneWithAuthoritiesById(Long id);

Expand All @@ -50,9 +53,6 @@ List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefor
@Query("UPDATE User u SET u.lastLoginTime = :lastLoginTime WHERE u.email = :userEmail")
void updateLastLoginTime(String userEmail, LocalDateTime lastLoginTime);

@Query("SELECT u FROM User u JOIN u.teams t WHERE t.id = :teamId")
Page<User> findUsersByTeamId(@Param("teamId") Long teamId, Pageable pageable);

@Query(
value =
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.flexwork.modules.usermanagement.repository;

import io.flexwork.modules.usermanagement.domain.UserTeam;
import io.flexwork.modules.usermanagement.domain.UserTeamId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserTeamRepository extends JpaRepository<UserTeam, UserTeamId> {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
import static io.flexwork.query.QueryUtils.createSpecification;

import io.flexwork.modules.usermanagement.domain.Team;
import io.flexwork.modules.usermanagement.domain.TeamRole;
import io.flexwork.modules.usermanagement.domain.User;
import io.flexwork.modules.usermanagement.domain.UserTeam;
import io.flexwork.modules.usermanagement.domain.UserTeamId;
import io.flexwork.modules.usermanagement.repository.TeamRepository;
import io.flexwork.modules.usermanagement.repository.TeamRoleRepository;
import io.flexwork.modules.usermanagement.repository.UserRepository;
import io.flexwork.modules.usermanagement.repository.UserTeamRepository;
import io.flexwork.modules.usermanagement.service.dto.TeamDTO;
import io.flexwork.modules.usermanagement.service.dto.UserDTO;
import io.flexwork.modules.usermanagement.service.dto.UserWithTeamRoleDTO;
import io.flexwork.modules.usermanagement.service.mapper.TeamMapper;
import io.flexwork.modules.usermanagement.service.mapper.UserMapper;
import io.flexwork.query.QueryDTO;
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
Expand All @@ -28,17 +35,25 @@ public class TeamService {

private final UserRepository userRepository;

private final UserTeamRepository userTeamRepository;

private final TeamRoleRepository teamRoleRepository;

private final TeamMapper teamMapper;

private final UserMapper userMapper;

public TeamService(
TeamRepository teamRepository,
UserRepository userRepository,
UserTeamRepository userTeamRepository,
TeamRoleRepository teamRoleRepository,
TeamMapper teamMapper,
UserMapper userMapper) {
this.teamRepository = teamRepository;
this.userRepository = userRepository;
this.userTeamRepository = userTeamRepository;
this.teamRoleRepository = teamRoleRepository;
this.teamMapper = teamMapper;
this.userMapper = userMapper;
}
Expand Down Expand Up @@ -91,27 +106,53 @@ public List<TeamDTO> findAllTeamsByUserId(Long userId) {
return teamRepository.findAllTeamsByUserId(userId).stream().map(teamMapper::toDto).toList();
}

public Page<UserWithTeamRoleDTO> getUsersByTeam(Long teamId, Pageable pageable) {
return teamRepository.findUsersByTeamId(teamId, pageable);
}

@Transactional(readOnly = true)
public List<UserDTO> findUsersNotInTeam(String searchTerm, Long teamId, Pageable pageable) {
return userMapper.toDtos(teamRepository.findUsersNotInTeam(searchTerm, teamId, pageable));
}

public void addUsersToTeam(List<Long> userIds, Long teamId) {
public void addUsersToTeam(List<Long> userIds, String roleName, Long teamId) {
// Fetch the authority entity
Team team =
teamRepository
.findById(teamId)
.orElseThrow(
() -> new IllegalArgumentException("Team not found: " + teamId));

TeamRole teamRole =
teamRoleRepository
.findById(roleName)
.orElseThrow(
() ->
new IllegalArgumentException(
"Invalid role name: " + roleName));

// Fetch the users and associate them with the authority
List<User> users = userRepository.findAllById(userIds);
for (User user : users) {
user.getTeams().add(team);

// Ensure all user IDs are valid
if (users.size() != userIds.size()) {
throw new IllegalArgumentException("Some user IDs are invalid.");
}

// Save all updated users
userRepository.saveAll(users);
List<UserTeam> userTeams =
users.stream()
.map(
user ->
new UserTeam(
new UserTeamId(
user.getId(), team.getId(), roleName),
user,
team,
teamRole))
.toList();

// Save all UserTeam entities in a batch
userTeamRepository.saveAll(userTeams);
}

@Transactional
Expand All @@ -134,4 +175,9 @@ public void removeUserFromTeam(Long userId, Long teamId) {
userRepository.save(user); // Save the updated user
}
}

public String getUserRoleInTeam(Long userId, Long teamId) {
String role = teamRepository.findUserRoleInTeam(userId, teamId);
return (StringUtils.isNotEmpty(role)) ? role : "Guest";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -312,25 +312,22 @@ public Page<UserDTO> findAllPublicUsers(Optional<QueryDTO> queryDTO, Pageable pa
return userRepository.findAll(spec, pageable).map(userMapper::toDto);
}

public Page<UserDTO> getUsersByTeam(Long teamId, Pageable pageable) {
return userRepository.findUsersByTeamId(teamId, pageable).map(userMapper::toDto);
}

@Transactional(readOnly = true)
public Optional<User> getUserWithAuthoritiesByEmail(String email) {
return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(email);
}

@Transactional(readOnly = true)
public Optional<UserDTO> getUserWithAuthoritiesById(Long id) {
return userRepository.findOneWithAuthoritiesById(id).map(userMapper::toDto);
public Optional<UserDTO> getUserWithManagerById(Long id) {
return userRepository.findOneWithManagerById(id).map(userMapper::toDto);
}

@Transactional(readOnly = true)
public Optional<User> getUserWithAuthorities() {
public Optional<UserDTO> getUserWithAuthorities() {
return SecurityUtils.getCurrentUserLogin()
.map(UserKey::getEmail)
.flatMap(userRepository::findOneWithAuthoritiesByEmailIgnoreCase);
.flatMap(userRepository::findOneWithAuthoritiesByEmailIgnoreCase)
.map(userMapper::toDto);
}

//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.flexwork.modules.usermanagement.service.dto;

import jakarta.validation.constraints.Email;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
public class UserWithTeamRoleDTO implements Serializable {

@EqualsAndHashCode.Include private Long id;

@Email @EqualsAndHashCode.Include private String email;

private String firstName;

private String lastName;

private String timezone;

private String imageUrl;

private String title;

private Long teamId;

private String teamRole;

public UserWithTeamRoleDTO(
Long id,
String email,
String firstName,
String lastName,
String timezone,
String imageUrl,
String title,
Long teamId,
String teamRole) {
this.id = id;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
this.timezone = timezone;
this.imageUrl = imageUrl;
this.title = title;
this.teamId = teamId;
this.teamRole = teamRole;
}
}
Loading
Loading