From 16ee413ce03580436156801b011433f8202d1f0a Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 11 Apr 2025 13:52:24 +0200 Subject: [PATCH] Refactor repositories and services for user and group management Added full CRUD functionality for users and groups with JDBC templates, including pagination and group membership management. Refactored service layers to streamline DTO transformations and repository interactions. --- .../lcc/controller/users/UserController.java | 4 +- .../repositories/users/GroupRepository.java | 51 +++++- .../repositories/users/UserRepository.java | 146 +++++++++++++++++- .../lcc/service/users/GroupService.java | 56 ++++++- .../avatic/lcc/service/users/UserService.java | 36 +++-- 5 files changed, 270 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/avatic/lcc/controller/users/UserController.java b/src/main/java/de/avatic/lcc/controller/users/UserController.java index 21577bd..b6367b2 100644 --- a/src/main/java/de/avatic/lcc/controller/users/UserController.java +++ b/src/main/java/de/avatic/lcc/controller/users/UserController.java @@ -46,7 +46,9 @@ public class UserController { } /** - * Updates the details of an existing user. + * Updates the details of an existing user or creates a new one if it does not exist. + * Users are identified by its workday id. If a group from the group membership does not exist + * it is simply ignored. * * @param user A UserDTO object containing the updated user details. * @return A ResponseEntity indicating the operation was successful. diff --git a/src/main/java/de/avatic/lcc/repositories/users/GroupRepository.java b/src/main/java/de/avatic/lcc/repositories/users/GroupRepository.java index e38e0ab..f9b1a77 100644 --- a/src/main/java/de/avatic/lcc/repositories/users/GroupRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/users/GroupRepository.java @@ -1,13 +1,62 @@ package de.avatic.lcc.repositories.users; +import de.avatic.lcc.model.materials.Material; import de.avatic.lcc.model.users.Group; +import de.avatic.lcc.repositories.MaterialRepository; +import de.avatic.lcc.repositories.pagination.SearchQueryPagination; +import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowCallbackHandler; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; @Repository public class GroupRepository { - public List listGroups() { + private final JdbcTemplate jdbcTemplate; + + public GroupRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Transactional + public SearchQueryResult listGroups(SearchQueryPagination pagination) { + + String query = "SELECT * FROM sys_group ORDER BY group_name LIMIT ? OFFSET ?"; + + var groups = jdbcTemplate.query(query, new GroupMapper(), + pagination.getLimit(), pagination.getOffset()); + + Integer totalCount = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM sys_group ORDER BY group_name LIMIT ? OFFSET ?", + Integer.class, pagination.getLimit(), pagination.getOffset() + ); + + return new SearchQueryResult<>(groups, pagination.getPage(), totalCount, pagination.getLimit()); } + + @Transactional + public void updateGroup(Group group) { + String query = "INSERT INTO sys_group (group_name, group_description) VALUES (?, ?) ON DUPLICATE KEY UPDATE group_description = ?"; + jdbcTemplate.update(query, group.getName(), group.getDescription(), group.getName(), group.getDescription()); + } + + private static class GroupMapper implements RowMapper { + + @Override + public Group mapRow(ResultSet rs, int rowNum) throws SQLException { + var group = new Group(); + + group.setId(rs.getInt("id")); + group.setName(rs.getString("group_name")); + group.setDescription(rs.getString("group_description")); + + return group; + } + } } diff --git a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java index 7a7b17d..6b642ab 100644 --- a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java @@ -1,23 +1,167 @@ package de.avatic.lcc.repositories.users; +import de.avatic.lcc.model.users.Group; import de.avatic.lcc.model.users.User; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; @Repository public class UserRepository { + private final JdbcTemplate jdbcTemplate; + + public UserRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + @Transactional public SearchQueryResult listUsers(SearchQueryPagination pagination) { - String query = "SELECT * FROM users"; + String query = """ + SELECT * + FROM sys_user + ORDER BY sys_user.workday_id LIMIT ? OFFSET ?"""; + + return new SearchQueryResult<>(jdbcTemplate.query(query, (rs, rowNum) -> { + var user = new User(); + + int id = rs.getInt("id"); + + user.setId(id); + user.setActive(rs.getBoolean("is_active")); + user.setEmail(rs.getString("email")); + user.setFirstName(rs.getString("firstname")); + user.setLastName(rs.getString("lastname")); + user.setWorkdayId(rs.getString("workday_id")); + + user.setGroups(getGroupMemberships(id)); + + return user; + }, pagination.getLimit(), pagination.getOffset()), pagination.getPage(), getTotalUserCount(), pagination.getLimit()); + } + private Integer getTotalUserCount() { + String query = "SELECT COUNT(*) FROM sys_user"; + return jdbcTemplate.queryForObject(query, Integer.class); + } + + private List getGroupMemberships(int id) { + + String query = "SELECT * FROM sys_group WHERE id IN (SELECT group_id FROM sys_user_group_mapping WHERE user_id = ?)"; + + return jdbcTemplate.query(query, (rs, rowNum) -> { + var group = new Group(); + + group.setId(rs.getInt("id")); + group.setName(rs.getString("group_name")); + group.setDescription(rs.getString("group_description")); + + return group; + }, id); + } + + @Transactional public void update(User user) { + Integer userId = findUserId(user.getWorkdayId()); + + List groupIds = findGroupIds(user.getGroups().stream().map(Group::getName).toList()); + + if(userId == null) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement( + "INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) " + + "VALUES (?, ?, ?, ?, ?)", + Statement.RETURN_GENERATED_KEYS + ); + ps.setString(1, user.getWorkdayId()); + ps.setString(2, user.getEmail()); + ps.setString(3, user.getFirstName()); + ps.setString(4, user.getLastName()); + ps.setBoolean(5, user.getActive()); + return ps; + }, keyHolder); + + userId = Objects.requireNonNull(keyHolder.getKey()).intValue(); + } + else { + String query = """ + UPDATE sys_user SET email = ?, firstname = ?, lastname = ?, workday_id = ?, is_active = ? WHERE id = ?"""; + + jdbcTemplate.update(query, user.getEmail(), user.getFirstName(), user.getLastName(), user.getWorkdayId(), user.getActive(), userId); + } + + updateUserGroupMappings(userId, groupIds); + + } + + private List findGroupIds(List groups) { + if (groups == null || groups.isEmpty()) { + return List.of(); + } + + // Create placeholders for parameterized query + String placeholders = String.join(",", Collections.nCopies(groups.size(), "?")); + String query = "SELECT id FROM sys_group WHERE group_name IN (" + placeholders + ")"; + + + return jdbcTemplate.query( + query, + ps -> { + for (int parameterIndex = 1; parameterIndex <= groups.size(); parameterIndex++) { + ps.setString(parameterIndex, groups.get(parameterIndex)); + } + }, + (rs, rowNum) -> rs.getInt("id") + ); + + } + + private void updateUserGroupMappings(Integer userId, List groups) { + + for (Integer groupId : groups) { + jdbcTemplate.update( + "INSERT IGNORE INTO sys_user_group_mapping (user_id, group_id) VALUES (?, ?)", + userId, groupId + ); + } + + String placeholders = String.join(",", Collections.nCopies(groups.size(), "?")); + String query = "DELETE FROM sys_user_group_mapping WHERE user_id = ? AND group_id NOT IN (" + placeholders + ")"; + + jdbcTemplate.query( + query, + ps -> { + for (int parameterIndex = 1; parameterIndex <= groups.size(); parameterIndex++) { + ps.setInt(parameterIndex, groups.get(parameterIndex)); + } + }, + (rs, rowNum) -> rs.getInt("id") + ); + } + + + private Integer findUserId(String workdayId) { + List results = jdbcTemplate.query("SELECT id FROM sys_user WHERE workday_id = ?", + (rs, rowNum) -> rs.getInt("id"), + workdayId); + + return results.isEmpty() ? null : results.getFirst(); + } + } diff --git a/src/main/java/de/avatic/lcc/service/users/GroupService.java b/src/main/java/de/avatic/lcc/service/users/GroupService.java index 23dc2e4..c468362 100644 --- a/src/main/java/de/avatic/lcc/service/users/GroupService.java +++ b/src/main/java/de/avatic/lcc/service/users/GroupService.java @@ -2,12 +2,18 @@ package de.avatic.lcc.service.users; import de.avatic.lcc.dto.users.GroupDTO; import de.avatic.lcc.dto.users.UserDTO; +import de.avatic.lcc.model.users.Group; +import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.repositories.users.GroupRepository; import org.springframework.stereotype.Service; import java.util.List; +/** + * Service class for managing group-related operations such as updating groups + * and retrieving group data. + */ @Service public class GroupService { private final GroupRepository groupRepository; @@ -16,16 +22,54 @@ public class GroupService { this.groupRepository = groupRepository; } - - + /** + * Updates an existing group in the repository. + * + * @param group the {@link GroupDTO} object containing updated group information + */ public void updateGroup(GroupDTO group) { - + groupRepository.updateGroup(fromGroupDTO(group)); } + /** + * Retrieves a paginated list of groups from the repository. + * + * @param page the page number to retrieve + * @param limit the maximum number of groups returned per page + * @return a {@link SearchQueryResult} containing {@link GroupDTO} objects and pagination metadata + */ public SearchQueryResult listGroups(int page, int limit) { + return SearchQueryResult.map(groupRepository.listGroups(new SearchQueryPagination(page, limit)), this::toGroupDTO); + } + + /** + * Maps a {@link GroupDTO} object to a {@link Group} entity. + * + * @param dto the {@link GroupDTO} to be mapped + * @return a {@link Group} entity with data populated from the DTO + */ + private Group fromGroupDTO(GroupDTO dto) { + var group = new Group(); + + group.setDescription(dto.getDescription()); + group.setName(dto.getName()); + + return group; + } + + /** + * Maps a {@link Group} entity to a {@link GroupDTO}. + * + * @param group the {@link Group} entity to be mapped + * @return a {@link GroupDTO} containing data from the entity + */ + private GroupDTO toGroupDTO(Group group) { + var dto = new GroupDTO(); + + dto.setDescription( group.getDescription()); + dto.setName(group.getName()); + + return dto; - groupRepository.listGroups(); - //todo translate - return null; } } diff --git a/src/main/java/de/avatic/lcc/service/users/UserService.java b/src/main/java/de/avatic/lcc/service/users/UserService.java index 8f225ee..d01c255 100644 --- a/src/main/java/de/avatic/lcc/service/users/UserService.java +++ b/src/main/java/de/avatic/lcc/service/users/UserService.java @@ -1,37 +1,45 @@ package de.avatic.lcc.service.users; import de.avatic.lcc.dto.users.UserDTO; -import de.avatic.lcc.model.users.User; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.repositories.users.UserRepository; -import de.avatic.lcc.service.transformer.users.UserDTOTransformer; +import de.avatic.lcc.service.transformer.users.UserTransformer; import org.springframework.stereotype.Service; -import java.util.List; - +/** + * Service class responsible for handling business logic related to users. + * Provides methods to retrieve and update user information. + */ @Service public class UserService { private final UserRepository userRepository; - private final UserDTOTransformer userDTOTransformer; + private final UserTransformer userTransformer; - public UserService(UserRepository userRepository, UserDTOTransformer userDTOTransformer) { + public UserService(UserRepository userRepository, UserTransformer userTransformer) { this.userRepository = userRepository; - this.userDTOTransformer = userDTOTransformer; + this.userTransformer = userTransformer; } + /** + * Retrieves a paginated list of users. + * + * @param page the page number to retrieve + * @param limit the number of users per page + * @return a SearchQueryResult containing the paginated user data + */ public SearchQueryResult listUsers(int page, int limit) { - return SearchQueryResult.map(userRepository.listUsers(new SearchQueryPagination(page, limit)), userDTOTransformer::toUserDTO); + return SearchQueryResult.map(userRepository.listUsers(new SearchQueryPagination(page, limit)), userTransformer::toUserDTO); } + /** + * Updates an existing user's information. + * + * @param user the UserDTO containing updated user information + */ public void updateUser(UserDTO user) { - - groupRepository.update - userRepository.update(fromUserDTO(user)); + userRepository.update(userTransformer.fromUserDTO(user)); } - private User fromUserDTO(UserDTO dto) { - return new User(); - } }