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.
This commit is contained in:
Jan 2025-04-11 13:52:24 +02:00
parent 8608fe349b
commit 16ee413ce0
5 changed files with 270 additions and 23 deletions

View file

@ -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. * @param user A UserDTO object containing the updated user details.
* @return A ResponseEntity indicating the operation was successful. * @return A ResponseEntity indicating the operation was successful.

View file

@ -1,13 +1,62 @@
package de.avatic.lcc.repositories.users; package de.avatic.lcc.repositories.users;
import de.avatic.lcc.model.materials.Material;
import de.avatic.lcc.model.users.Group; 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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List; import java.util.List;
@Repository @Repository
public class GroupRepository { public class GroupRepository {
public List<Group> listGroups() { private final JdbcTemplate jdbcTemplate;
public GroupRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public SearchQueryResult<Group> 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<Group> {
@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;
}
}
} }

View file

@ -1,23 +1,167 @@
package de.avatic.lcc.repositories.users; package de.avatic.lcc.repositories.users;
import de.avatic.lcc.model.users.Group;
import de.avatic.lcc.model.users.User; import de.avatic.lcc.model.users.User;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult; 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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; 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.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Repository @Repository
public class UserRepository { public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional @Transactional
public SearchQueryResult<User> listUsers(SearchQueryPagination pagination) { public SearchQueryResult<User> 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<Group> 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 @Transactional
public void update(User user) { public void update(User user) {
Integer userId = findUserId(user.getWorkdayId());
List<Integer> 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<Integer> findGroupIds(List<String> 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<Integer> 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<Integer> results = jdbcTemplate.query("SELECT id FROM sys_user WHERE workday_id = ?",
(rs, rowNum) -> rs.getInt("id"),
workdayId);
return results.isEmpty() ? null : results.getFirst();
}
} }

View file

@ -2,12 +2,18 @@ package de.avatic.lcc.service.users;
import de.avatic.lcc.dto.users.GroupDTO; import de.avatic.lcc.dto.users.GroupDTO;
import de.avatic.lcc.dto.users.UserDTO; 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.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.users.GroupRepository; import de.avatic.lcc.repositories.users.GroupRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
/**
* Service class for managing group-related operations such as updating groups
* and retrieving group data.
*/
@Service @Service
public class GroupService { public class GroupService {
private final GroupRepository groupRepository; private final GroupRepository groupRepository;
@ -16,16 +22,54 @@ public class GroupService {
this.groupRepository = groupRepository; 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) { 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<GroupDTO> listGroups(int page, int limit) { public SearchQueryResult<GroupDTO> 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;
} }
} }

View file

@ -1,37 +1,45 @@
package de.avatic.lcc.service.users; package de.avatic.lcc.service.users;
import de.avatic.lcc.dto.users.UserDTO; 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.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.users.UserRepository; 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 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 @Service
public class UserService { public class UserService {
private final UserRepository userRepository; 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.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<UserDTO> listUsers(int page, int limit) { public SearchQueryResult<UserDTO> 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) { public void updateUser(UserDTO user) {
userRepository.update(userTransformer.fromUserDTO(user));
groupRepository.update
userRepository.update(fromUserDTO(user));
} }
private User fromUserDTO(UserDTO dto) {
return new User();
}
} }