mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:19:35 +01:00
fix(backend): lückenloses Error-Handling und Logging im Infrastructure Layer
Stack-Traces in allen JPA-Repositories per logger.trace() bewahren, bevor sie durch Result.failure() auf die Message reduziert werden. Security-Layer erhält eigene Handler-Beans (ApiAuthenticationEntryPoint, ApiAccessDeniedHandler) mit konsistentem ErrorResponse-Format statt Inline-Lambdas. JWT-Filter loggt Validierungsfehler auf WARN statt DEBUG und fängt IllegalArgumentException. RoleController nutzt jetzt das Exception-Pattern der anderen Controller statt eines leeren 500-Bodys. GlobalExceptionHandler differenziert zwischen fachlichen Domain-Fehlern (WARN) und technischen Repository-Fehlern (ERROR) und fängt auch checked Exceptions als Catch-All.
This commit is contained in:
parent
fbed3f899f
commit
e4f0665086
13 changed files with 241 additions and 45 deletions
|
|
@ -4,6 +4,8 @@ import de.effigenix.domain.masterdata.*;
|
|||
import de.effigenix.infrastructure.masterdata.persistence.mapper.ArticleMapper;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -17,6 +19,8 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaArticleRepository implements ArticleRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaArticleRepository.class);
|
||||
|
||||
private final ArticleJpaRepository jpaRepository;
|
||||
private final ArticleMapper mapper;
|
||||
|
||||
|
|
@ -32,6 +36,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
.map(mapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +49,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +62,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByCategory", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +75,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByStatus", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +87,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
jpaRepository.save(mapper.toEntity(article));
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +99,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
jpaRepository.deleteById(article.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +109,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByArticleNumber(articleNumber.value()));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByArticleNumber", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import de.effigenix.infrastructure.masterdata.persistence.entity.FrameContractEn
|
|||
import de.effigenix.infrastructure.masterdata.persistence.mapper.CustomerMapper;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -18,6 +20,8 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaCustomerRepository implements CustomerRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaCustomerRepository.class);
|
||||
|
||||
private final CustomerJpaRepository jpaRepository;
|
||||
private final FrameContractJpaRepository frameContractJpaRepository;
|
||||
private final CustomerMapper mapper;
|
||||
|
|
@ -40,6 +44,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
var fcEntity = frameContractJpaRepository.findByCustomerId(id.value()).orElse(null);
|
||||
return Result.success(Optional.of(mapper.toDomain(entityOpt.get(), fcEntity)));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +60,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +76,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByType", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +92,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByStatus", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -104,6 +112,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +125,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
jpaRepository.deleteById(customer.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +135,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByName(name.value()));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByName", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import de.effigenix.domain.masterdata.ProductCategoryRepository;
|
|||
import de.effigenix.infrastructure.masterdata.persistence.mapper.ProductCategoryMapper;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -20,6 +22,8 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaProductCategoryRepository.class);
|
||||
|
||||
private final ProductCategoryJpaRepository jpaRepository;
|
||||
private final ProductCategoryMapper mapper;
|
||||
|
||||
|
|
@ -35,6 +39,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
|||
.map(mapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +52,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +64,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
|||
jpaRepository.save(mapper.toEntity(category));
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +76,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
|||
jpaRepository.deleteById(category.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +86,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByName(name.value()));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByName", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import de.effigenix.domain.masterdata.*;
|
|||
import de.effigenix.infrastructure.masterdata.persistence.mapper.SupplierMapper;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -17,6 +19,8 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaSupplierRepository implements SupplierRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaSupplierRepository.class);
|
||||
|
||||
private final SupplierJpaRepository jpaRepository;
|
||||
private final SupplierMapper mapper;
|
||||
|
||||
|
|
@ -32,6 +36,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
.map(mapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +49,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +62,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByStatus", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +74,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
jpaRepository.save(mapper.toEntity(supplier));
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +86,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
jpaRepository.deleteById(supplier.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +96,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByName(name.value()));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByName", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package de.effigenix.infrastructure.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.effigenix.infrastructure.usermanagement.web.dto.ErrorResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class ApiAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiAccessDeniedHandler.class);
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public ApiAccessDeniedHandler(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) throws IOException {
|
||||
logger.warn("Access denied for {}: {}", request.getRequestURI(), accessDeniedException.getMessage());
|
||||
|
||||
var errorResponse = ErrorResponse.from(
|
||||
"FORBIDDEN",
|
||||
"Access denied",
|
||||
HttpStatus.FORBIDDEN.value(),
|
||||
request.getRequestURI()
|
||||
);
|
||||
|
||||
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getOutputStream(), errorResponse);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package de.effigenix.infrastructure.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.effigenix.infrastructure.usermanagement.web.dto.ErrorResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiAuthenticationEntryPoint.class);
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public ApiAuthenticationEntryPoint(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
logger.warn("Authentication failed for {}: {}", request.getRequestURI(), authException.getMessage());
|
||||
|
||||
var errorResponse = ErrorResponse.from(
|
||||
"UNAUTHORIZED",
|
||||
"Authentication required",
|
||||
HttpStatus.UNAUTHORIZED.value(),
|
||||
request.getRequestURI()
|
||||
);
|
||||
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getOutputStream(), errorResponse);
|
||||
}
|
||||
}
|
||||
|
|
@ -101,13 +101,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
} catch (JwtException | SecurityException e) {
|
||||
} catch (JwtException | SecurityException | IllegalArgumentException e) {
|
||||
// Token validation failed - clear SecurityContext and continue
|
||||
// Spring Security will return 401 Unauthorized for protected endpoints
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
// Log the error for debugging
|
||||
logger.debug("JWT authentication failed: " + e.getMessage(), e);
|
||||
logger.warn("JWT authentication failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
// Continue filter chain (even if authentication failed)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
private final ApiAuthenticationEntryPoint authenticationEntryPoint;
|
||||
private final ApiAccessDeniedHandler accessDeniedHandler;
|
||||
|
||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||
ApiAuthenticationEntryPoint authenticationEntryPoint,
|
||||
ApiAccessDeniedHandler accessDeniedHandler) {
|
||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||
this.authenticationEntryPoint = authenticationEntryPoint;
|
||||
this.accessDeniedHandler = accessDeniedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,26 +98,10 @@ public class SecurityConfig {
|
|||
.anyRequest().denyAll()
|
||||
)
|
||||
|
||||
// Exception Handling: Return 401 Unauthorized for authentication failures
|
||||
// Exception Handling: Return 401/403 with consistent ErrorResponse format
|
||||
.exceptionHandling(exception -> exception
|
||||
.authenticationEntryPoint((request, response, authException) -> {
|
||||
response.setStatus(401);
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write(
|
||||
"{\"error\":\"Unauthorized\",\"message\":\"" +
|
||||
authException.getMessage() +
|
||||
"\"}"
|
||||
);
|
||||
})
|
||||
.accessDeniedHandler((request, response, accessDeniedException) -> {
|
||||
response.setStatus(403);
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write(
|
||||
"{\"error\":\"Forbidden\",\"message\":\"" +
|
||||
accessDeniedException.getMessage() +
|
||||
"\"}"
|
||||
);
|
||||
})
|
||||
.authenticationEntryPoint(authenticationEntryPoint)
|
||||
.accessDeniedHandler(accessDeniedHandler)
|
||||
)
|
||||
|
||||
// Add JWT Authentication Filter before Spring Security's authentication filters
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import de.effigenix.shared.common.Result;
|
|||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
|
@ -32,6 +34,8 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaRoleRepository implements RoleRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaRoleRepository.class);
|
||||
|
||||
private final RoleJpaRepository jpaRepository;
|
||||
private final RoleMapper roleMapper;
|
||||
|
||||
|
|
@ -47,6 +51,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
.map(roleMapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +63,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
.map(roleMapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByName", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +76,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +88,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
jpaRepository.save(roleMapper.toEntity(role));
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +100,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
jpaRepository.deleteById(role.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +110,7 @@ public class JpaRoleRepository implements RoleRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByName(name));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByName", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import de.effigenix.shared.common.Result;
|
|||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
|
@ -32,6 +34,7 @@ import java.util.stream.Collectors;
|
|||
@Transactional(readOnly = true)
|
||||
public class JpaUserRepository implements UserRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JpaUserRepository.class);
|
||||
private final UserJpaRepository jpaRepository;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
|
|
@ -47,6 +50,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.map(userMapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findById", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +62,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.map(userMapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByUsername", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +74,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.map(userMapper::toDomain);
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByEmail", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +87,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByBranchId", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +100,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findByStatus", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +113,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in findAll", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +125,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
jpaRepository.save(userMapper.toEntity(user));
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in save", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +137,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
jpaRepository.deleteById(user.id().value());
|
||||
return Result.success(null);
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in delete", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +147,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByUsername(username));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByUsername", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -145,6 +157,7 @@ public class JpaUserRepository implements UserRepository {
|
|||
try {
|
||||
return Result.success(jpaRepository.existsByEmail(email));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByEmail", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ public class AuthController {
|
|||
return ResponseEntity.ok(LoginResponse.from(token));
|
||||
} catch (RuntimeException ex) {
|
||||
logger.warn("Token refresh failed: {}", ex.getMessage());
|
||||
logger.trace("Token refresh exception details", ex);
|
||||
throw new AuthenticationFailedException(
|
||||
new UserError.InvalidCredentials()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -103,30 +103,37 @@ public class RoleController {
|
|||
ActorId actorId = extractActorId(authentication);
|
||||
logger.info("Listing roles by actor: {}", actorId.value());
|
||||
|
||||
return switch (roleRepository.findAll()) {
|
||||
case Result.Failure<RepositoryError, List<Role>> f -> {
|
||||
logger.error("Failed to load roles: {}", f.error().message());
|
||||
yield ResponseEntity.internalServerError().build();
|
||||
var result = roleRepository.findAll();
|
||||
if (result.isFailure()) {
|
||||
throw new RoleDomainErrorException(result.unsafeGetError());
|
||||
}
|
||||
case Result.Success<RepositoryError, List<Role>> s -> {
|
||||
List<RoleDTO> roles = s.value().stream()
|
||||
|
||||
List<RoleDTO> roles = result.unsafeGetValue().stream()
|
||||
.map(RoleDTO::from)
|
||||
.collect(Collectors.toList());
|
||||
logger.info("Found {} roles", roles.size());
|
||||
yield ResponseEntity.ok(roles);
|
||||
}
|
||||
};
|
||||
return ResponseEntity.ok(roles);
|
||||
}
|
||||
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
/**
|
||||
* Extracts ActorId from Spring Security Authentication.
|
||||
*/
|
||||
private ActorId extractActorId(Authentication authentication) {
|
||||
if (authentication == null || authentication.getName() == null) {
|
||||
throw new IllegalStateException("No authentication found in SecurityContext");
|
||||
}
|
||||
return ActorId.of(authentication.getName());
|
||||
}
|
||||
|
||||
public static class RoleDomainErrorException extends RuntimeException {
|
||||
private final RepositoryError error;
|
||||
|
||||
public RoleDomainErrorException(RepositoryError error) {
|
||||
super(error.message());
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public RepositoryError getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import de.effigenix.infrastructure.masterdata.web.controller.ArticleController;
|
|||
import de.effigenix.infrastructure.masterdata.web.controller.CustomerController;
|
||||
import de.effigenix.infrastructure.masterdata.web.controller.ProductCategoryController;
|
||||
import de.effigenix.infrastructure.masterdata.web.controller.SupplierController;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.infrastructure.masterdata.web.exception.MasterDataErrorHttpStatusMapper;
|
||||
import de.effigenix.infrastructure.usermanagement.web.controller.AuthController;
|
||||
import de.effigenix.infrastructure.usermanagement.web.controller.RoleController;
|
||||
import de.effigenix.infrastructure.usermanagement.web.controller.UserController;
|
||||
import de.effigenix.infrastructure.usermanagement.web.dto.ErrorResponse;
|
||||
|
||||
|
|
@ -89,7 +91,7 @@ public class GlobalExceptionHandler {
|
|||
) {
|
||||
UserError error = ex.getError();
|
||||
int status = UserErrorHttpStatusMapper.toHttpStatus(error);
|
||||
logger.warn("Domain error: {} - {}", error.code(), error.message());
|
||||
logDomainError("User", error.code(), error.message(), status);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
error.code(),
|
||||
|
|
@ -108,7 +110,7 @@ public class GlobalExceptionHandler {
|
|||
) {
|
||||
ArticleError error = ex.getError();
|
||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
||||
logger.warn("Article domain error: {} - {}", error.code(), error.message());
|
||||
logDomainError("Article", error.code(), error.message(), status);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
error.code(),
|
||||
|
|
@ -127,7 +129,7 @@ public class GlobalExceptionHandler {
|
|||
) {
|
||||
ProductCategoryError error = ex.getError();
|
||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
||||
logger.warn("ProductCategory domain error: {} - {}", error.code(), error.message());
|
||||
logDomainError("ProductCategory", error.code(), error.message(), status);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
error.code(),
|
||||
|
|
@ -146,7 +148,7 @@ public class GlobalExceptionHandler {
|
|||
) {
|
||||
SupplierError error = ex.getError();
|
||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
||||
logger.warn("Supplier domain error: {} - {}", error.code(), error.message());
|
||||
logDomainError("Supplier", error.code(), error.message(), status);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
error.code(),
|
||||
|
|
@ -165,7 +167,7 @@ public class GlobalExceptionHandler {
|
|||
) {
|
||||
CustomerError error = ex.getError();
|
||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
||||
logger.warn("Customer domain error: {} - {}", error.code(), error.message());
|
||||
logDomainError("Customer", error.code(), error.message(), status);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
error.code(),
|
||||
|
|
@ -177,6 +179,24 @@ public class GlobalExceptionHandler {
|
|||
return ResponseEntity.status(status).body(errorResponse);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RoleController.RoleDomainErrorException.class)
|
||||
public ResponseEntity<ErrorResponse> handleRoleDomainError(
|
||||
RoleController.RoleDomainErrorException ex,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
RepositoryError error = ex.getError();
|
||||
logger.error("Role repository error: {}", error.message());
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
"REPOSITORY_ERROR",
|
||||
error.message(),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
request.getRequestURI()
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles validation errors from @Valid annotations.
|
||||
*
|
||||
|
|
@ -325,7 +345,7 @@ public class GlobalExceptionHandler {
|
|||
RuntimeException ex,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
logger.error("Unexpected error: {}", ex.getMessage(), ex);
|
||||
logger.error("Unexpected runtime error: {}", ex.getMessage(), ex);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
"INTERNAL_ERROR",
|
||||
|
|
@ -338,4 +358,31 @@ public class GlobalExceptionHandler {
|
|||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(errorResponse);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleException(
|
||||
Exception ex,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
logger.error("Unexpected checked exception: {}", ex.getMessage(), ex);
|
||||
|
||||
ErrorResponse errorResponse = ErrorResponse.from(
|
||||
"INTERNAL_ERROR",
|
||||
"An unexpected error occurred. Please contact support.",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
request.getRequestURI()
|
||||
);
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(errorResponse);
|
||||
}
|
||||
|
||||
private void logDomainError(String context, String code, String message, int status) {
|
||||
if (status >= 500) {
|
||||
logger.error("{} domain error: {} - {}", context, code, message);
|
||||
} else {
|
||||
logger.warn("{} domain error: {} - {}", context, code, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue