Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/main/java/com/security/clients/MembersClient.java

This file was deleted.

33 changes: 29 additions & 4 deletions src/main/java/com/security/controllers/AdminUserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.security.dtos.autorization.RoleChangeRequestDTO;
import com.security.dtos.autorization.RolesResponseDTO;
import com.security.annotations.AdminAccess;
import com.security.dtos.roles.RoleDetailsDto;
import com.security.dtos.roles.RoleStatisticsDto;
import com.security.services.UserAccountService;
import com.security.services.UserRoleService;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -14,6 +16,8 @@
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.UUID;

@RestController
Expand All @@ -32,22 +36,22 @@ public ResponseEntity<RolesResponseDTO> getRoles(@PathVariable UUID id) {
return ResponseEntity.ok(userRoleService.getUserRoles(id));
}


@Operation(summary = "Agregar un rol a un usuario")
@PostMapping("/{id}/roles")
@AdminAccess
public ResponseEntity<AuthResponseDTO> addRole(@PathVariable UUID id, @Valid @RequestBody RoleChangeRequestDTO request) {
public ResponseEntity<AuthResponseDTO> addRole(@PathVariable UUID id,
@Valid @RequestBody RoleChangeRequestDTO request) {
return ResponseEntity.ok(userRoleService.addRoleToUser(id, request.role()));
}

@Operation(summary = "Quitar un rol a un usuario")
@DeleteMapping("/{id}/roles")
@AdminAccess
public ResponseEntity<AuthResponseDTO> deleteRole(@PathVariable UUID id, @RequestBody RoleChangeRequestDTO requestDTO) {
public ResponseEntity<AuthResponseDTO> deleteRole(@PathVariable UUID id,
@RequestBody RoleChangeRequestDTO requestDTO) {
return ResponseEntity.ok(userRoleService.removeRoleFromUser(id, requestDTO.role()));
}


@Operation(summary = "Desactivar cuenta de usuario")
@PatchMapping("/{id}/deactivate")
@AdminAccess
Expand All @@ -69,4 +73,25 @@ public ResponseEntity<AuthResponseDTO> activate(@PathVariable UUID id,
return ResponseEntity.ok(userAccountService.activateUser(id, admin));
}

@Operation(summary = "Obtener estadísticas de usuarios por roles y estados")
@GetMapping("")
@AdminAccess
public ResponseEntity<RoleStatisticsDto> getUserStatistics() {
return ResponseEntity.ok(userRoleService.getUserStatistics());
}

@Operation(summary = "Obtener lista de roles con descripciones")
@GetMapping("/roles")
@AdminAccess
public ResponseEntity<List<RoleDetailsDto>> getRoleDetails() {
return ResponseEntity.ok(userRoleService.getRoleDetails());
}

@Operation(summary = "Obtener estadísticas de usuarios")
@GetMapping("/provider")
@AdminAccess
public ResponseEntity<Map<String, Object>> getUserProviderStatistics() {
return ResponseEntity.ok(userAccountService.getUserStatistics());
}

}
8 changes: 1 addition & 7 deletions src/main/java/com/security/dtos/autorization/UserDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,12 @@
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {

private UUID id;

private String username;

private String email;

private String firstName;

private String lastName;

private Boolean enabled;

private String provider;
private Set<RoleDTO> roles;
}
9 changes: 9 additions & 0 deletions src/main/java/com/security/dtos/roles/RoleDetailsDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.security.dtos.roles;

import lombok.*;
@Data
@AllArgsConstructor
public class RoleDetailsDto {
private String roleName;
private String description;
}
16 changes: 16 additions & 0 deletions src/main/java/com/security/dtos/roles/RoleStatisticsDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.security.dtos.roles;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Map;

@Data
@AllArgsConstructor
public class RoleStatisticsDto {
private long totalUsers;
private long activeUsers;
private long inactiveUsers;
private long suspendedUsers;
private Map<String, Long> roleCounts;
}
2 changes: 2 additions & 0 deletions src/main/java/com/security/entity/RoleEntity.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.security.entity;

import com.security.config.audit.Audit;
import com.security.config.audit.AuditListener;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -15,6 +16,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditListener.class)
public class RoleEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/security/mappers/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
public interface UserMapper {

@Mapping(target = "roles", source = "roles")
@Mapping(target = "provider", expression = "java(userEntity.getProvider() != null ? userEntity.getProvider().name() : null)")
UserDTO toDTO(UserEntity userEntity);

// @Mapping(target = "roles", source = "roles")
SimpleUserDto toSimpleDto(UserEntity user);

@Mapping(target = "password", ignore = true)
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/security/repository/RoleRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.security.entity.RoleEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

@Repository
public interface RoleRepository extends JpaRepository<RoleEntity, UUID> {
Optional<RoleEntity> findByName(String name);
}
5 changes: 2 additions & 3 deletions src/main/java/com/security/repository/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import com.security.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, UUID> {
Optional<UserEntity> findByEmail(String email);

boolean existsByEmail(String email);


}
61 changes: 47 additions & 14 deletions src/main/java/com/security/services/Impl/AuthServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import com.security.repository.UserRepository;
import com.security.services.AuthService;
import com.security.services.TokenService;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
Expand All @@ -40,7 +43,6 @@ public class AuthServiceImpl implements AuthService {
private final PasswordEncoder passwordEncoder;
private final RoleRepository roleRepository;
private final UserMapper userMapper;
private final JwtEncoder jwtEncoder;
private final JwtDecoder jwtDecoder;
private final KafkaTemplate<String, Object> kafkaTemplate;
private final TokenService tokenService;
Expand All @@ -50,6 +52,8 @@ public class AuthServiceImpl implements AuthService {
private final Set<String> blacklistedTokens = ConcurrentHashMap.newKeySet();

@Override
@CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "authenticateUserFallback")
@Retry(name = "databaseRetry")
public LoginResponseDTO authenticateUser(LoginRequestDTO request) {
log.info("Authenticating user: {}", request.getEmail());

Expand Down Expand Up @@ -80,7 +84,14 @@ public LoginResponseDTO authenticateUser(LoginRequestDTO request) {
.build();
}

private LoginResponseDTO authenticateUserFallback(LoginResponseDTO request, Throwable throwable) {
log.error("Fallback activado para authenticateUser - Error {}", throwable.getMessage());
throw new AuthenticationException("Servicio de autenticación no disponible.Intente de nuevo más tarde.");
}

@Override
@CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "refreshTokenFallback")
@Retry(name = "databaseRetry")
public LoginResponseDTO refreshToken(String refreshToken) {
log.info("Refreshing token");

Expand Down Expand Up @@ -123,6 +134,11 @@ public LoginResponseDTO refreshToken(String refreshToken) {
}
}

private LoginResponseDTO refreshTokenFallback(String refreshToken, Throwable throwable) {
log.error("Fallback activado para refreshToken - Error: {}", throwable.getMessage());
throw new AuthenticationException("Servicio de renovación de tokens no disponible");
}

@Override
public LoginResponseDTO createTokensForOAuth2User(UserEntity user) {
log.info("Creando tokens para usuarios OAuth2: {}", user.getEmail());
Expand Down Expand Up @@ -171,6 +187,8 @@ public void logout(String accessToken) {


@Override
@CircuitBreaker(name = "authServiceCircuitBreaker", fallbackMethod = "registerUserFallback")
@Retry(name = "databaseRetry")
public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) {
if (userRepository.existsByEmail(registerRequestDto.email())) {
throw new AuthenticationException("Error al registrar usuario intente de nuevo");
Expand All @@ -190,19 +208,7 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) {

userRepository.save(user);

CreatedUserEvent event = new CreatedUserEvent(
user.getId().toString(),
registerRequestDto.firstName(),
registerRequestDto.lastName(),
registerRequestDto.dni(),
registerRequestDto.phone(),
registerRequestDto.email(),
null
);

log.info("Enviando evento {}", event);
kafkaTemplate.send("user-created-event-topic", event);
log.info("Evento enviado {}", event);
sendUserCreatedEvent(user, registerRequestDto);

return LoginResponseDTO.builder()
.accessToken(tokenService.generateAccessToken(user))
Expand All @@ -215,5 +221,32 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) {
.build();
}

private LoginResponseDTO registerUserFallback(RegisterRequestDto registerRequestDto, Throwable throwable) {
log.error("Fallback activado para registerUser- Error {}", throwable.getMessage());
throw new AuthenticationException("Servicio de registro temporalmente no disponible");
}


@CircuitBreaker(name = "kafkaCircuitBreaker", fallbackMethod = "sendUserCreatedEventFallback")
@Retry(name = "kafkaRetry")
private void sendUserCreatedEvent(UserEntity user, RegisterRequestDto registerRequestDto) {
CreatedUserEvent event = new CreatedUserEvent(
user.getId().toString(),
registerRequestDto.firstName(),
registerRequestDto.lastName(),
registerRequestDto.dni(),
registerRequestDto.phone(),
registerRequestDto.email(),
null
);

log.info("Enviando evento de usuario creado: {}", event);
kafkaTemplate.send("user-created-event-topic", event);
log.info("Evento enviado correctamente");
}

private void sendUserCreatedEventFallback(UserEntity user, RegisterRequestDto registerRequestDto, Throwable throwable) {
log.error("Fallback de Kafka - No se pudo enviar evento de usuario creado. Error: {}", throwable.getMessage());
log.warn("Evento no enviado será reintentado posteriormente para usuario: {}", user.getEmail());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.security.dtos.auth.AuthResponseDTO;
import com.security.entity.UserEntity;
import com.security.enums.AuthProvider;
import com.security.repository.UserRepository;
import com.security.config.audit.Audit;
import com.security.services.UserAccountService;
Expand All @@ -12,6 +13,7 @@
import org.springframework.web.server.ResponseStatusException;

import java.time.Instant;
import java.util.Map;
import java.util.UUID;

@Service
Expand Down Expand Up @@ -47,6 +49,24 @@ public AuthResponseDTO deactivateUser(UUID userId, String reason, String admin)

}

@Override
@Transactional(readOnly = true)
public Map<String, Object> getUserStatistics() {
long totalUsers = userRepository.count();
long googleUsers = userRepository.findAll().stream()
.filter(user -> user.getProvider() == AuthProvider.GOOGLE)
.count();
long localUsers = userRepository.findAll().stream()
.filter(user -> user.getProvider() == AuthProvider.LOCAL)
.count();

return Map.of(
"totalUsers", totalUsers,
"googleUsers", googleUsers,
"localUsers", localUsers
);
}


@Override
public AuthResponseDTO activateUser(UUID userId, String admin) {
Expand Down
29 changes: 26 additions & 3 deletions src/main/java/com/security/services/Impl/UserRoleServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.security.dtos.auth.AuthResponseDTO;
import com.security.dtos.autorization.RolesResponseDTO;
import com.security.dtos.roles.RoleDetailsDto;
import com.security.dtos.roles.RoleStatisticsDto;
import com.security.entity.RoleEntity;
import com.security.entity.UserEntity;
import com.security.repository.RoleRepository;
Expand All @@ -15,9 +17,7 @@
import org.springframework.web.server.ResponseStatusException;

import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

@Service
Expand Down Expand Up @@ -70,4 +70,27 @@ public AuthResponseDTO removeRoleFromUser(UUID userId, String roleName) {
userRepository.save(user);
return new AuthResponseDTO(true, "Rol removido", Instant.now());
}

@Override
public RoleStatisticsDto getUserStatistics() {
List<UserEntity> users = userRepository.findAll();

long totalUsers = users.size();
long activeUsers = users.stream().filter(UserEntity::isEnabled).count();
long inactiveUsers = users.stream().filter(user -> !user.isEnabled()).count();
long suspendedUsers = users.stream().filter(user -> !user.isAccountNonLocked()).count();

Map<String, Long> roleCounts = users.stream()
.flatMap(user -> user.getRoles().stream())
.collect(Collectors.groupingBy(role -> role.getName().toUpperCase(), Collectors.counting()));

return new RoleStatisticsDto(totalUsers, activeUsers, inactiveUsers, suspendedUsers, roleCounts);
}

@Override
public List<RoleDetailsDto> getRoleDetails() {
return roleRepository.findAll().stream()
.map(role -> new RoleDetailsDto(role.getName(), role.getDescription()))
.collect(Collectors.toList());
}
}
Loading