package com.gcsc.guide.service; import com.gcsc.guide.dto.StatsResponse; import com.gcsc.guide.dto.UserResponse; import com.gcsc.guide.entity.Role; import com.gcsc.guide.entity.User; import com.gcsc.guide.entity.UserStatus; import com.gcsc.guide.exception.BusinessException; import com.gcsc.guide.exception.ResourceNotFoundException; import com.gcsc.guide.repository.LoginHistoryRepository; import com.gcsc.guide.repository.RoleRepository; import com.gcsc.guide.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.HashSet; import java.util.List; import java.util.Set; @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; private final RoleRepository roleRepository; private final LoginHistoryRepository loginHistoryRepository; @Transactional(readOnly = true) public List getUsers(String status) { List users; if (status != null && !status.isBlank()) { UserStatus userStatus = UserStatus.valueOf(status.toUpperCase()); users = userRepository.findByStatus(userStatus); } else { users = userRepository.findAll(); } return users.stream().map(UserResponse::from).toList(); } @Transactional public UserResponse approveUser(Long userId) { User user = findUserById(userId); if (user.getStatus() != UserStatus.PENDING) { throw new BusinessException("PENDING 상태의 사용자만 승인할 수 있습니다 (현재: " + user.getStatus() + ")"); } user.activate(); return UserResponse.from(userRepository.save(user)); } @Transactional public UserResponse rejectUser(Long userId) { User user = findUserById(userId); if (user.getStatus() != UserStatus.PENDING) { throw new BusinessException("PENDING 상태의 사용자만 거절할 수 있습니다 (현재: " + user.getStatus() + ")"); } user.reject(); return UserResponse.from(userRepository.save(user)); } @Transactional public UserResponse disableUser(Long userId) { User user = findUserById(userId); if (user.getStatus() != UserStatus.ACTIVE) { throw new BusinessException("ACTIVE 상태의 사용자만 비활성화할 수 있습니다 (현재: " + user.getStatus() + ")"); } user.disable(); return UserResponse.from(userRepository.save(user)); } @Transactional public UserResponse updateUserRoles(Long userId, List roleIds) { User user = findUserById(userId); Set roles = new HashSet<>(roleRepository.findAllById(roleIds)); if (roles.size() != roleIds.size()) { throw new BusinessException("일부 롤을 찾을 수 없습니다"); } user.updateRoles(roles); userRepository.save(user); return UserResponse.from(userRepository.findByIdWithRoles(userId).orElseThrow()); } @Transactional public UserResponse grantAdmin(Long userId) { User user = findUserById(userId); user.grantAdmin(); return UserResponse.from(userRepository.save(user)); } @Transactional public UserResponse revokeAdmin(Long userId) { User user = findUserById(userId); user.revokeAdmin(); return UserResponse.from(userRepository.save(user)); } @Transactional(readOnly = true) public StatsResponse getStats() { long totalUsers = userRepository.count(); long activeUsers = userRepository.countByStatus(UserStatus.ACTIVE); long pendingUsers = userRepository.countByStatus(UserStatus.PENDING); long rejectedUsers = userRepository.countByStatus(UserStatus.REJECTED); long disabledUsers = userRepository.countByStatus(UserStatus.DISABLED); long todayLogins = loginHistoryRepository.countByLoginAtAfter( LocalDate.now().atStartOfDay()); long totalRoles = roleRepository.count(); return new StatsResponse( totalUsers, activeUsers, pendingUsers, rejectedUsers, disabledUsers, todayLogins, totalRoles ); } private User findUserById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new ResourceNotFoundException("사용자", userId)); } }