package com.sshmanager.service; import com.sshmanager.dto.CreateUserRequest; import com.sshmanager.dto.UpdateUserRequest; import com.sshmanager.dto.UserDto; import com.sshmanager.entity.User; import com.sshmanager.exception.InvalidOperationException; import com.sshmanager.exception.NotFoundException; import com.sshmanager.repository.UserRepository; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @Service public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } public List listUsers() { return userRepository.findAll().stream() .map(this::toDto) .collect(Collectors.toList()); } public UserDto getUser(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new NotFoundException("User not found: " + id)); return toDto(user); } public UserDto createUser(CreateUserRequest request) { if (request.getUsername() == null || request.getUsername().trim().isEmpty()) { throw new InvalidOperationException("Username is required"); } if (request.getPassword() == null || request.getPassword().length() < 8) { throw new InvalidOperationException("Password must be at least 8 characters"); } if (userRepository.existsByUsername(request.getUsername().trim())) { throw new InvalidOperationException("Username already exists: " + request.getUsername()); } User user = new User(); user.setUsername(request.getUsername().trim()); user.setPasswordHash(passwordEncoder.encode(request.getPassword())); user.setDisplayName(request.getDisplayName() != null ? request.getDisplayName().trim() : request.getUsername().trim()); user.setRole(request.getRole() != null ? request.getRole() : "ROLE_USER"); if (!"ROLE_ADMIN".equals(user.getRole()) && !"ROLE_USER".equals(user.getRole())) { user.setRole("ROLE_USER"); } user.setEnabled(true); user.setPasswordChangedAt(Instant.now()); user.setCreatedAt(Instant.now()); user.setUpdatedAt(Instant.now()); User saved = userRepository.save(user); return toDto(saved); } public UserDto updateUser(Long id, UpdateUserRequest request, Long currentUserId) { User user = userRepository.findById(id) .orElseThrow(() -> new NotFoundException("User not found: " + id)); if (request.getDisplayName() != null) { user.setDisplayName(request.getDisplayName().trim()); } if (request.getRole() != null) { if ("ROLE_ADMIN".equals(request.getRole()) || "ROLE_USER".equals(request.getRole())) { // Cannot change the last admin to a regular user if ("ROLE_ADMIN".equals(user.getRole()) && "ROLE_USER".equals(request.getRole()) && isLastAdmin(id)) { throw new InvalidOperationException("Cannot change the last admin user to a regular user"); } user.setRole(request.getRole()); } } if (request.getEnabled() != null) { // Cannot disable yourself if (user.getId().equals(currentUserId) && !request.getEnabled()) { throw new InvalidOperationException("You cannot disable your own account"); } // Cannot disable the last admin if (!request.getEnabled() && "ROLE_ADMIN".equals(user.getRole()) && isLastAdmin(id)) { throw new InvalidOperationException("Cannot disable the last admin user"); } user.setEnabled(request.getEnabled()); } user.setUpdatedAt(Instant.now()); User saved = userRepository.save(user); return toDto(saved); } public void deleteUser(Long id, Long currentUserId) { if (id.equals(currentUserId)) { throw new InvalidOperationException("You cannot delete your own account"); } User user = userRepository.findById(id) .orElseThrow(() -> new NotFoundException("User not found: " + id)); // Cannot delete the last admin if ("ROLE_ADMIN".equals(user.getRole()) && isLastAdmin(id)) { throw new InvalidOperationException("Cannot delete the last admin user"); } userRepository.delete(user); } public void resetPassword(Long id, String newPassword) { if (newPassword == null || newPassword.length() < 8) { throw new InvalidOperationException("New password must be at least 8 characters"); } User user = userRepository.findById(id) .orElseThrow(() -> new NotFoundException("User not found: " + id)); user.setPasswordHash(passwordEncoder.encode(newPassword)); user.setPasswordChangedAt(Instant.EPOCH); user.setUpdatedAt(Instant.now()); userRepository.save(user); } public UserDto createRegularUser(String username, String password, String displayName) { if (username == null || username.trim().isEmpty()) { throw new InvalidOperationException("Username is required"); } if (password == null || password.length() < 8) { throw new InvalidOperationException("Password must be at least 8 characters"); } if (userRepository.existsByUsername(username.trim())) { throw new InvalidOperationException("Username already exists: " + username); } User user = new User(); user.setUsername(username.trim()); user.setPasswordHash(passwordEncoder.encode(password)); user.setDisplayName(displayName != null && !displayName.trim().isEmpty() ? displayName.trim() : username.trim()); user.setRole("ROLE_USER"); user.setEnabled(true); user.setPasswordChangedAt(Instant.now()); user.setCreatedAt(Instant.now()); user.setUpdatedAt(Instant.now()); User saved = userRepository.save(user); return toDto(saved); } private boolean isLastAdmin(Long id) { User user = userRepository.findById(id).orElse(null); if (user == null || !"ROLE_ADMIN".equals(user.getRole())) { return false; } long adminCount = userRepository.findAll().stream() .filter(u -> "ROLE_ADMIN".equals(u.getRole())) .count(); return adminCount <= 1; } private UserDto toDto(User user) { return new UserDto( user.getId(), user.getUsername(), user.getDisplayName(), user.getRole(), user.getEnabled(), user.getCreatedAt(), user.getUpdatedAt(), user.getPasswordChangedAt() ); } }