package zw.qantra.tm.domain.services; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import zw.qantra.tm.domain.dtos.AuthResponse; import zw.qantra.tm.domain.dtos.LoginRequest; import zw.qantra.tm.domain.dtos.RegisterRequest; import zw.qantra.tm.domain.models.User; import zw.qantra.tm.domain.models.Workspace; import zw.qantra.tm.domain.repositories.UserRepository; import zw.qantra.tm.utils.JwtUtils; import java.util.Optional; import java.util.UUID; @Service @RequiredArgsConstructor public class UserService implements UserDetailsService { @Getter private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtUtils jwtUtils; private final TransactionService transactionService; private final WorkspaceService workspaceService; public User fetchUserByUsername(String username) { return userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); } public AuthResponse register(RegisterRequest request) { if (userRepository.existsByUsername(request.getUsername())) { throw new RuntimeException("Username already exists"); } if (userRepository.existsByEmail(request.getEmail())) { throw new RuntimeException("Email already exists"); } User user = new User(); user.setUsername(request.getUsername()); user.setPassword(passwordEncoder.encode(request.getPassword())); user.setEmail(request.getEmail()); user.setFirstName(request.getFirstName()); user.setLastName(request.getLastName()); user.setPhone(request.getPhone()); user.setWorkflowId(request.getWorkflowId()); user.setPhotoUrl(request.getPhotoUrl()); user.setDeleted(false); User savedUser = userRepository.save(user); String token = jwtUtils.generateToken(savedUser); // reassign anonymous transactions to the new user // if we fail we move on, not a critical step try { transactionService.reassignTransactions(request.getTempUid(), savedUser.getId().toString()); }catch (Exception e) { e.printStackTrace(); } return new AuthResponse(token, "Bearer", savedUser.getUsername(), savedUser.getEmail(), savedUser.getPhone(), savedUser.getFirstName(), savedUser.getLastName(), savedUser.getId()); } public AuthResponse login(LoginRequest request) { User user = userRepository.findByUsername(request.getUsername()) .orElseThrow(() -> new RuntimeException("Invalid username or password")); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new RuntimeException("Invalid username or password"); } String token = jwtUtils.generateToken(user); return new AuthResponse(token, "Bearer", user.getUsername(), user.getEmail(), user.getPhone(), user.getFirstName(), user.getLastName(), user.getId()); } public Page findAllUsers(Specification spec, Pageable pageable) { return userRepository.findAll(spec, pageable); } public Optional getUser(UUID id) { return userRepository.findById(id); } @Transactional public User createUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); user.setDeleted(false); return userRepository.save(user); } @Transactional public User updateUser(UUID id, User userDetails) { return userRepository.findById(id) .map(existingUser -> { existingUser.setFirstName(userDetails.getFirstName()); existingUser.setLastName(userDetails.getLastName()); existingUser.setEmail(userDetails.getEmail()); existingUser.setPhone(userDetails.getPhone()); existingUser.setPhotoUrl(userDetails.getPhotoUrl()); existingUser.setWorkflowId(userDetails.getWorkflowId()); existingUser.setEnabled(userDetails.isEnabled()); existingUser.setAccountNonExpired(userDetails.isAccountNonExpired()); existingUser.setAccountNonLocked(userDetails.isAccountNonLocked()); existingUser.setCredentialsNonExpired(userDetails.isCredentialsNonExpired()); if (userDetails.getPassword() != null && !userDetails.getPassword().isEmpty()) { existingUser.setPassword(passwordEncoder.encode(userDetails.getPassword())); } return userRepository.save(existingUser); }) .orElseThrow(() -> new RuntimeException("User not found with id: " + id)); } @Transactional public boolean deleteUser(UUID id) { return userRepository.findById(id) .map(user -> { user.setDeleted(true); userRepository.save(user); return true; }) .orElse(false); } }