UserService.java
package api.services;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import api.dtos.UserDto;
import api.entities.MimeType;
import api.entities.Role;
import api.entities.S3Object;
import api.entities.User;
import api.exceptions.DuplicateEntityException;
import api.exceptions.EntityNotFoundException;
import api.mapper.UserMapper;
import api.repositories.UserRepository;
import lombok.extern.slf4j.Slf4j;
/**
* {@link UserService}.
*/
@Slf4j
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserMapper userMapper;
@Autowired
private S3ObjectService s3ObjectService;
@Autowired
private MimeTypeService mimeTypeService;
@Value("${api.s3.avatar.max:5000000}")
private long maxAvatarSize;
private static final Set<MediaType> ALLOWED_AVATAR_TYPES = Set.of(
MediaType.IMAGE_PNG,
MediaType.IMAGE_JPEG,
MediaType.parseMediaType("image/svg+xml"),
MediaType.IMAGE_GIF
);
/**
* Load user with authorities by username.
*
* @param username Username
* @return {@link User}
*/
@Override
@Transactional(readOnly = true)
public User loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsernameWithAuthorities(username)
.orElseThrow(() -> new UsernameNotFoundException("User '" + username + "' found"));
return user;
}
/**
* Find user.
*
* @param id User id
* @return {@link Optional} {@link User}
*/
@Transactional(readOnly = true)
public Optional<User> find(int id) {
return userRepository.findById(id);
}
/**
* Find user.
*
* @param username Username
* @return {@link Optional} {@link User}
*/
@Transactional(readOnly = true)
public Optional<User> find(String username) {
return userRepository.findByUsername(username);
}
/**
* Check if user exists.
*
* @param id User id
* @return true if user exists
*/
@Transactional(readOnly = true)
public boolean exists(int id) {
return userRepository.existsById(id);
}
/**
* Check if user exists.
*
* @param username Username
* @return true if user exists
*/
@Transactional(readOnly = true)
public boolean exists(String username) {
return find(username).isPresent();
}
/**
* Get all users.
*
* @param pageable {@link Pageable}
* @return User ids.
*/
@Transactional(readOnly = true)
public Page<User> getAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
/**
* Get all users with a given role.
*
* @param role Role
* @return {@link Set} of {@link User}
*/
@Transactional(readOnly = true)
public Set<User> getAllContainingRole(Role role) {
Set<User> user = userRepository.findAllByRolesContaining(role);
return user;
}
/**
* Get user.
*
* @param id User id
* @return {@link User}
*/
@Transactional(readOnly = true)
public User get(int id) {
User user = find(id)
.orElseThrow(() -> EntityNotFoundException.fromUser(id));
return user;
}
/**
* Get user.
*
* @param username Username
* @return {@link User}
*/
@Transactional(readOnly = true)
public User get(String username) {
User user = find(username)
.orElseThrow(() -> EntityNotFoundException.fromUser(username));
return user;
}
/**
* Update user.
*
* @param id User id
* @param dto Updated info
* @return {@link User}
*/
@Transactional
public User update(int id, UserDto dto) {
return update(get(id), dto);
}
/**
* Update user.
*
* @param user User
* @param dto Updated info
* @return {@link User}
*/
@Transactional
public User update(User user, UserDto dto) {
if (dto.getUsername() != null && !StringUtils.hasText(dto.getUsername())) {
throw new IllegalArgumentException("Username must not be empty.");
}
if (StringUtils.hasText(dto.getUsername())
&& !Objects.equals(dto.getUsername(), user.getUsername())
&& exists(dto.getUsername())
) {
throw DuplicateEntityException.fromUser(dto.getUsername());
}
userMapper.update(user, dto);
return save(user);
}
/**
* Save user.
*
* @param user User
* @return {@link User}
*/
@Transactional
public User save(User user) {
User saved = userRepository.save(user);
log.info("User saved: " + user);
return saved;
}
/**
* Delete user.
*
* @param id User id
*/
@Transactional
public void delete(int id) {
if (!exists(id)) {
throw EntityNotFoundException.fromUser(id);
}
userRepository.deleteById(id);
log.info("User deleted: " + id);
}
/**
* Delete user.
*
* @param user User
*/
@Transactional
public void delete(User user) {
userRepository.delete(user);
log.info("User deleted: " + user);
}
/**
* Get user roles.
*
* @param id User
* @return User's roles
*/
@Transactional(readOnly = true)
public Set<Role> getRoles(int id) {
Set<Role> roles = get(id).getRoles();
Hibernate.initialize(roles);
return roles;
}
/**
* Get user roles.
*
* @param user User
* @return User's roles
*/
@Transactional(readOnly = true)
public Set<Role> getRoles(User user) {
return getRoles(user.getId());
}
/**
* Update user avatar.
*
* @param id User id
* @param avatar Avatar
* @throws IOException Failed to get input stream
*/
@Transactional
public void updateAvatar(int id, MultipartFile avatar) throws IOException {
updateAvatar(get(id), avatar);
}
/**
* Update user avatar.
*
* @param user User
* @param avatar Avatar
* @throws IOException Failed to get input stream
*/
@Transactional
public void updateAvatar(User user, MultipartFile avatar) throws IOException {
if (avatar.isEmpty()) {
throw new IllegalArgumentException("File must not be empty.");
}
if (avatar.getSize() > maxAvatarSize) {
throw new IllegalArgumentException("File size exceeds limit (" + maxAvatarSize + " bytes).");
}
String contentType = avatar.getContentType();
if (contentType == null || !ALLOWED_AVATAR_TYPES.contains(MediaType.parseMediaType(contentType))) {
throw new IllegalArgumentException("Only PNG, JPEG, SVG and GIF images are allowed.");
}
final S3Object oldAvatar = user.getAvatar();
MimeType mimeType = mimeTypeService.getOrCreateByName(contentType);
S3Object newAvatar = S3Object.builder()
.key(S3ObjectService.AVATAR_DIR + user.getId() + "_" + UUID.randomUUID())
.size(avatar.getSize())
.mimeType(mimeType)
.build();
s3ObjectService.save(newAvatar, avatar.getInputStream());
user.setAvatar(newAvatar);
save(user);
if (oldAvatar != null) {
s3ObjectService.delete(oldAvatar);
}
}
/**
* Delete user avatar.
*
* @param id User id
*/
@Transactional
public void deleteAvatar(int id) {
deleteAvatar(get(id));
}
/**
* Delete user avatar.
*
* @param user User
*/
@Transactional
public void deleteAvatar(User user) {
S3Object s3Object = user.getAvatar();
if (s3Object == null) {
throw EntityNotFoundException.fromUserAvatar(user.getUsername());
}
user.setAvatar(null);
save(user);
s3ObjectService.delete(s3Object);
}
}