diff --git a/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java b/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java new file mode 100644 index 000000000..580e1148d --- /dev/null +++ b/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java @@ -0,0 +1,20 @@ +package stirling.software.proprietary.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class TeamWithUserCountDTO { + private Long id; + private String name; + private Long userCount; + + // Constructor for JPQL projection + public TeamWithUserCountDTO(Long id, String name, Long userCount) { + this.id = id; + this.name = name; + this.userCount = userCount; + } +} \ No newline at end of file diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java index 608c96b0b..c2fbcce79 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java @@ -33,6 +33,7 @@ import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.proprietary.model.Team; +import stirling.software.proprietary.security.database.repository.UserRepository; import stirling.software.proprietary.security.model.AuthenticationType; import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.api.user.UsernameAndPass; @@ -54,7 +55,7 @@ public class UserController { private final SessionPersistentRegistry sessionRegistry; private final ApplicationProperties applicationProperties; private final TeamRepository teamRepository; - + private final UserRepository userRepository; @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/register") public String register(@ModelAttribute UsernameAndPass requestModel, Model model) @@ -210,6 +211,7 @@ public class UserController { @RequestParam(name = "username", required = true) String username, @RequestParam(name = "password", required = false) String password, @RequestParam(name = "role") String role, + @RequestParam(name = "teamId", required = false) Long teamId, @RequestParam(name = "authType") String authType, @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) @@ -243,14 +245,23 @@ public class UserController { // If the role ID is not valid, redirect with an error message return new RedirectView("/adminSettings?messageType=invalidRole", true); } - Team team = teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null); + + // Use teamId if provided, otherwise use default team + Long effectiveTeamId = teamId; + if (effectiveTeamId == null) { + Team defaultTeam = teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null); + if (defaultTeam != null) { + effectiveTeamId = defaultTeam.getId(); + } + } + if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) { - userService.saveUser(username, AuthenticationType.SSO, team, role); + userService.saveUser(username, AuthenticationType.SSO, effectiveTeamId, role); } else { if (password.isBlank()) { return new RedirectView("/adminSettings?messageType=invalidPassword", true); } - userService.saveUser(username, password, team, role, forceChange); + userService.saveUser(username, password, effectiveTeamId, role, forceChange); } return new RedirectView( "/adminSettings", // Redirect to account page after adding the user @@ -262,6 +273,7 @@ public class UserController { public RedirectView changeRole( @RequestParam(name = "username") String username, @RequestParam(name = "role") String role, + @RequestParam(name = "teamId", required = false) Long teamId, Authentication authentication) throws SQLException, UnsupportedProviderException { Optional userOpt = userService.findByUsernameIgnoreCase(username); @@ -289,6 +301,15 @@ public class UserController { return new RedirectView("/adminSettings?messageType=invalidRole", true); } User user = userOpt.get(); + + // Update the team if a teamId is provided + if (teamId != null) { + teamRepository.findById(teamId).ifPresent(team -> { + user.setTeam(team); + userRepository.save(user); + }); + } + userService.changeRole(user, role); return new RedirectView( "/adminSettings", // Redirect to account page after adding the user diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java b/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java index f062e67d6..68a7035c7 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java @@ -2,7 +2,6 @@ package stirling.software.proprietary.security.controller.web; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; @@ -14,8 +13,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import stirling.software.proprietary.model.Team; +import stirling.software.proprietary.model.dto.TeamWithUserCountDTO; import stirling.software.proprietary.security.database.repository.SessionRepository; import stirling.software.proprietary.security.database.repository.UserRepository; import stirling.software.proprietary.security.model.User; @@ -24,17 +25,18 @@ import stirling.software.proprietary.security.repository.TeamRepository; @Controller @RequestMapping("/teams") @RequiredArgsConstructor +@Slf4j public class TeamWebController { private final TeamRepository teamRepository; - private final UserRepository userRepository; private final SessionRepository sessionRepository; + private final UserRepository userRepository; @GetMapping @PreAuthorize("hasRole('ROLE_ADMIN')") public String listTeams(Model model) { - // Get all teams with their users - List teams = teamRepository.findAllWithUsers(); + // Get teams with user counts using a DTO projection + List teamsWithCounts = teamRepository.findAllTeamsWithUserCount(); // Get the latest activity for each team List teamActivities = sessionRepository.findLatestActivityByTeam(); @@ -42,45 +44,41 @@ public class TeamWebController { // Convert the query results to a map for easy access in the view Map teamLastRequest = new HashMap<>(); for (Object[] result : teamActivities) { - // For JPQL query with aliases Long teamId = (Long) result[0]; // teamId alias Date lastActivity = (Date) result[1]; // lastActivity alias - teamLastRequest.put(teamId, lastActivity); } - model.addAttribute("teams", teams); + // Add data to the model + model.addAttribute("teamsWithCounts", teamsWithCounts); model.addAttribute("teamLastRequest", teamLastRequest); + return "enterprise/teams"; } @GetMapping("/{id}") @PreAuthorize("hasRole('ROLE_ADMIN')") public String viewTeamDetails(@PathVariable("id") Long id, Model model) { - // Get the team with its users - Team team = - teamRepository - .findById(id) - .orElseThrow(() -> new RuntimeException("Team not found")); - - List members = userRepository.findAllByTeam(team); - team.setUsers(new HashSet<>(members)); - + // Get the team + Team team = teamRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Team not found")); + + // Get users for this team directly using the direct query + List teamUsers = userRepository.findAllByTeamId(id); + // Get the latest session for each user in the team List userSessions = sessionRepository.findLatestSessionByTeamId(id); // Create a map of username to last request date Map userLastRequest = new HashMap<>(); - - // Process results from JPQL query for (Object[] result : userSessions) { String username = (String) result[0]; // username alias Date lastRequest = (Date) result[1]; // lastRequest alias - userLastRequest.put(username, lastRequest); } model.addAttribute("team", team); + model.addAttribute("teamUsers", teamUsers); model.addAttribute("userLastRequest", userLastRequest); return "enterprise/team-details"; } diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java b/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java index 98d4d510c..6420e6c09 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java @@ -27,8 +27,11 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.team IS NULL") List findAllWithoutTeam(); - @Query("SELECT u FROM User u LEFT JOIN FETCH u.team") + @Query(value = "SELECT u FROM User u LEFT JOIN FETCH u.team") List findAllWithTeam(); + + @Query("SELECT u FROM User u JOIN FETCH u.authorities WHERE u.team.id = :teamId") + List findAllByTeamId(@Param("teamId") Long teamId); long countByTeam(Team team); diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/repository/TeamRepository.java b/proprietary/src/main/java/stirling/software/proprietary/security/repository/TeamRepository.java index e3f72d533..691b41e18 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/repository/TeamRepository.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/repository/TeamRepository.java @@ -5,16 +5,19 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import stirling.software.proprietary.model.Team; +import stirling.software.proprietary.model.dto.TeamWithUserCountDTO; @Repository public interface TeamRepository extends JpaRepository { Optional findByName(String name); - @Query("SELECT t FROM Team t LEFT JOIN FETCH t.users") - List findAllWithUsers(); + @Query("SELECT new stirling.software.proprietary.model.dto.TeamWithUserCountDTO(t.id, t.name, COUNT(u)) " + + "FROM Team t LEFT JOIN t.users u GROUP BY t.id, t.name") + List findAllTeamsWithUserCount(); boolean existsByNameIgnoreCase(String name); } diff --git a/proprietary/src/main/resources/templates/enterprise/team-details.html b/proprietary/src/main/resources/templates/enterprise/team-details.html index 3dff979c0..de10011b4 100644 --- a/proprietary/src/main/resources/templates/enterprise/team-details.html +++ b/proprietary/src/main/resources/templates/enterprise/team-details.html @@ -24,13 +24,9 @@
-
-
Team ID:
-
1
-
Total Members:
-
1
+
1
@@ -55,7 +51,7 @@ - + 1 username Role @@ -76,7 +72,7 @@
-