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.infrastructure.masterdata.persistence.mapper.ArticleMapper;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -17,6 +19,8 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaArticleRepository implements ArticleRepository {
|
public class JpaArticleRepository implements ArticleRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaArticleRepository.class);
|
||||||
|
|
||||||
private final ArticleJpaRepository jpaRepository;
|
private final ArticleJpaRepository jpaRepository;
|
||||||
private final ArticleMapper mapper;
|
private final ArticleMapper mapper;
|
||||||
|
|
||||||
|
|
@ -32,6 +36,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
.map(mapper::toDomain);
|
.map(mapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +49,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +62,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByCategory", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +75,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByStatus", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +87,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
jpaRepository.save(mapper.toEntity(article));
|
jpaRepository.save(mapper.toEntity(article));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +99,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
jpaRepository.deleteById(article.id().value());
|
jpaRepository.deleteById(article.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +109,7 @@ public class JpaArticleRepository implements ArticleRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByArticleNumber(articleNumber.value()));
|
return Result.success(jpaRepository.existsByArticleNumber(articleNumber.value()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByArticleNumber", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
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.infrastructure.masterdata.persistence.mapper.CustomerMapper;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -18,6 +20,8 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaCustomerRepository implements CustomerRepository {
|
public class JpaCustomerRepository implements CustomerRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaCustomerRepository.class);
|
||||||
|
|
||||||
private final CustomerJpaRepository jpaRepository;
|
private final CustomerJpaRepository jpaRepository;
|
||||||
private final FrameContractJpaRepository frameContractJpaRepository;
|
private final FrameContractJpaRepository frameContractJpaRepository;
|
||||||
private final CustomerMapper mapper;
|
private final CustomerMapper mapper;
|
||||||
|
|
@ -40,6 +44,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
var fcEntity = frameContractJpaRepository.findByCustomerId(id.value()).orElse(null);
|
var fcEntity = frameContractJpaRepository.findByCustomerId(id.value()).orElse(null);
|
||||||
return Result.success(Optional.of(mapper.toDomain(entityOpt.get(), fcEntity)));
|
return Result.success(Optional.of(mapper.toDomain(entityOpt.get(), fcEntity)));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +60,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +76,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByType", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +92,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByStatus", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +112,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
|
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +125,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
jpaRepository.deleteById(customer.id().value());
|
jpaRepository.deleteById(customer.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +135,7 @@ public class JpaCustomerRepository implements CustomerRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByName(name.value()));
|
return Result.success(jpaRepository.existsByName(name.value()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByName", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
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.infrastructure.masterdata.persistence.mapper.ProductCategoryMapper;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -20,6 +22,8 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaProductCategoryRepository.class);
|
||||||
|
|
||||||
private final ProductCategoryJpaRepository jpaRepository;
|
private final ProductCategoryJpaRepository jpaRepository;
|
||||||
private final ProductCategoryMapper mapper;
|
private final ProductCategoryMapper mapper;
|
||||||
|
|
||||||
|
|
@ -35,6 +39,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
.map(mapper::toDomain);
|
.map(mapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +52,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +64,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
jpaRepository.save(mapper.toEntity(category));
|
jpaRepository.save(mapper.toEntity(category));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +76,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
jpaRepository.deleteById(category.id().value());
|
jpaRepository.deleteById(category.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +86,7 @@ public class JpaProductCategoryRepository implements ProductCategoryRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByName(name.value()));
|
return Result.success(jpaRepository.existsByName(name.value()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByName", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
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.infrastructure.masterdata.persistence.mapper.SupplierMapper;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -17,6 +19,8 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaSupplierRepository implements SupplierRepository {
|
public class JpaSupplierRepository implements SupplierRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaSupplierRepository.class);
|
||||||
|
|
||||||
private final SupplierJpaRepository jpaRepository;
|
private final SupplierJpaRepository jpaRepository;
|
||||||
private final SupplierMapper mapper;
|
private final SupplierMapper mapper;
|
||||||
|
|
||||||
|
|
@ -32,6 +36,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
.map(mapper::toDomain);
|
.map(mapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +49,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +62,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByStatus", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +74,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
jpaRepository.save(mapper.toEntity(supplier));
|
jpaRepository.save(mapper.toEntity(supplier));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +86,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
jpaRepository.deleteById(supplier.id().value());
|
jpaRepository.deleteById(supplier.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +96,7 @@ public class JpaSupplierRepository implements SupplierRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByName(name.value()));
|
return Result.success(jpaRepository.existsByName(name.value()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByName", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
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);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (JwtException | SecurityException e) {
|
} catch (JwtException | SecurityException | IllegalArgumentException e) {
|
||||||
// Token validation failed - clear SecurityContext and continue
|
// Token validation failed - clear SecurityContext and continue
|
||||||
// Spring Security will return 401 Unauthorized for protected endpoints
|
// Spring Security will return 401 Unauthorized for protected endpoints
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
|
|
||||||
// Log the error for debugging
|
logger.warn("JWT authentication failed: " + e.getMessage());
|
||||||
logger.debug("JWT authentication failed: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue filter chain (even if authentication failed)
|
// Continue filter chain (even if authentication failed)
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
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.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||||
|
this.authenticationEntryPoint = authenticationEntryPoint;
|
||||||
|
this.accessDeniedHandler = accessDeniedHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,26 +98,10 @@ public class SecurityConfig {
|
||||||
.anyRequest().denyAll()
|
.anyRequest().denyAll()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exception Handling: Return 401 Unauthorized for authentication failures
|
// Exception Handling: Return 401/403 with consistent ErrorResponse format
|
||||||
.exceptionHandling(exception -> exception
|
.exceptionHandling(exception -> exception
|
||||||
.authenticationEntryPoint((request, response, authException) -> {
|
.authenticationEntryPoint(authenticationEntryPoint)
|
||||||
response.setStatus(401);
|
.accessDeniedHandler(accessDeniedHandler)
|
||||||
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() +
|
|
||||||
"\"}"
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add JWT Authentication Filter before Spring Security's authentication filters
|
// 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.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -32,6 +34,8 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaRoleRepository implements RoleRepository {
|
public class JpaRoleRepository implements RoleRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaRoleRepository.class);
|
||||||
|
|
||||||
private final RoleJpaRepository jpaRepository;
|
private final RoleJpaRepository jpaRepository;
|
||||||
private final RoleMapper roleMapper;
|
private final RoleMapper roleMapper;
|
||||||
|
|
||||||
|
|
@ -47,6 +51,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
.map(roleMapper::toDomain);
|
.map(roleMapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +63,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
.map(roleMapper::toDomain);
|
.map(roleMapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByName", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +76,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +88,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
jpaRepository.save(roleMapper.toEntity(role));
|
jpaRepository.save(roleMapper.toEntity(role));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +100,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
jpaRepository.deleteById(role.id().value());
|
jpaRepository.deleteById(role.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +110,7 @@ public class JpaRoleRepository implements RoleRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByName(name));
|
return Result.success(jpaRepository.existsByName(name));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByName", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
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.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -32,6 +34,7 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public class JpaUserRepository implements UserRepository {
|
public class JpaUserRepository implements UserRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JpaUserRepository.class);
|
||||||
private final UserJpaRepository jpaRepository;
|
private final UserJpaRepository jpaRepository;
|
||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
|
|
||||||
|
|
@ -47,6 +50,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.map(userMapper::toDomain);
|
.map(userMapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +62,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.map(userMapper::toDomain);
|
.map(userMapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByUsername", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +74,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.map(userMapper::toDomain);
|
.map(userMapper::toDomain);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByEmail", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +87,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByBranchId", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +100,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByStatus", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +113,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +125,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
jpaRepository.save(userMapper.toEntity(user));
|
jpaRepository.save(userMapper.toEntity(user));
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +137,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
jpaRepository.deleteById(user.id().value());
|
jpaRepository.deleteById(user.id().value());
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in delete", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,6 +147,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByUsername(username));
|
return Result.success(jpaRepository.existsByUsername(username));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByUsername", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +157,7 @@ public class JpaUserRepository implements UserRepository {
|
||||||
try {
|
try {
|
||||||
return Result.success(jpaRepository.existsByEmail(email));
|
return Result.success(jpaRepository.existsByEmail(email));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByEmail", e);
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ public class AuthController {
|
||||||
return ResponseEntity.ok(LoginResponse.from(token));
|
return ResponseEntity.ok(LoginResponse.from(token));
|
||||||
} catch (RuntimeException ex) {
|
} catch (RuntimeException ex) {
|
||||||
logger.warn("Token refresh failed: {}", ex.getMessage());
|
logger.warn("Token refresh failed: {}", ex.getMessage());
|
||||||
|
logger.trace("Token refresh exception details", ex);
|
||||||
throw new AuthenticationFailedException(
|
throw new AuthenticationFailedException(
|
||||||
new UserError.InvalidCredentials()
|
new UserError.InvalidCredentials()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -103,30 +103,37 @@ public class RoleController {
|
||||||
ActorId actorId = extractActorId(authentication);
|
ActorId actorId = extractActorId(authentication);
|
||||||
logger.info("Listing roles by actor: {}", actorId.value());
|
logger.info("Listing roles by actor: {}", actorId.value());
|
||||||
|
|
||||||
return switch (roleRepository.findAll()) {
|
var result = roleRepository.findAll();
|
||||||
case Result.Failure<RepositoryError, List<Role>> f -> {
|
if (result.isFailure()) {
|
||||||
logger.error("Failed to load roles: {}", f.error().message());
|
throw new RoleDomainErrorException(result.unsafeGetError());
|
||||||
yield ResponseEntity.internalServerError().build();
|
}
|
||||||
}
|
|
||||||
case Result.Success<RepositoryError, List<Role>> s -> {
|
List<RoleDTO> roles = result.unsafeGetValue().stream()
|
||||||
List<RoleDTO> roles = s.value().stream()
|
.map(RoleDTO::from)
|
||||||
.map(RoleDTO::from)
|
.collect(Collectors.toList());
|
||||||
.collect(Collectors.toList());
|
logger.info("Found {} roles", roles.size());
|
||||||
logger.info("Found {} roles", roles.size());
|
return ResponseEntity.ok(roles);
|
||||||
yield ResponseEntity.ok(roles);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Helper Methods ====================
|
// ==================== Helper Methods ====================
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts ActorId from Spring Security Authentication.
|
|
||||||
*/
|
|
||||||
private ActorId extractActorId(Authentication authentication) {
|
private ActorId extractActorId(Authentication authentication) {
|
||||||
if (authentication == null || authentication.getName() == null) {
|
if (authentication == null || authentication.getName() == null) {
|
||||||
throw new IllegalStateException("No authentication found in SecurityContext");
|
throw new IllegalStateException("No authentication found in SecurityContext");
|
||||||
}
|
}
|
||||||
return ActorId.of(authentication.getName());
|
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.CustomerController;
|
||||||
import de.effigenix.infrastructure.masterdata.web.controller.ProductCategoryController;
|
import de.effigenix.infrastructure.masterdata.web.controller.ProductCategoryController;
|
||||||
import de.effigenix.infrastructure.masterdata.web.controller.SupplierController;
|
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.masterdata.web.exception.MasterDataErrorHttpStatusMapper;
|
||||||
import de.effigenix.infrastructure.usermanagement.web.controller.AuthController;
|
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.controller.UserController;
|
||||||
import de.effigenix.infrastructure.usermanagement.web.dto.ErrorResponse;
|
import de.effigenix.infrastructure.usermanagement.web.dto.ErrorResponse;
|
||||||
|
|
||||||
|
|
@ -89,7 +91,7 @@ public class GlobalExceptionHandler {
|
||||||
) {
|
) {
|
||||||
UserError error = ex.getError();
|
UserError error = ex.getError();
|
||||||
int status = UserErrorHttpStatusMapper.toHttpStatus(error);
|
int status = UserErrorHttpStatusMapper.toHttpStatus(error);
|
||||||
logger.warn("Domain error: {} - {}", error.code(), error.message());
|
logDomainError("User", error.code(), error.message(), status);
|
||||||
|
|
||||||
ErrorResponse errorResponse = ErrorResponse.from(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
error.code(),
|
error.code(),
|
||||||
|
|
@ -108,7 +110,7 @@ public class GlobalExceptionHandler {
|
||||||
) {
|
) {
|
||||||
ArticleError error = ex.getError();
|
ArticleError error = ex.getError();
|
||||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
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(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
error.code(),
|
error.code(),
|
||||||
|
|
@ -127,7 +129,7 @@ public class GlobalExceptionHandler {
|
||||||
) {
|
) {
|
||||||
ProductCategoryError error = ex.getError();
|
ProductCategoryError error = ex.getError();
|
||||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
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(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
error.code(),
|
error.code(),
|
||||||
|
|
@ -146,7 +148,7 @@ public class GlobalExceptionHandler {
|
||||||
) {
|
) {
|
||||||
SupplierError error = ex.getError();
|
SupplierError error = ex.getError();
|
||||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
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(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
error.code(),
|
error.code(),
|
||||||
|
|
@ -165,7 +167,7 @@ public class GlobalExceptionHandler {
|
||||||
) {
|
) {
|
||||||
CustomerError error = ex.getError();
|
CustomerError error = ex.getError();
|
||||||
int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
|
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(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
error.code(),
|
error.code(),
|
||||||
|
|
@ -177,6 +179,24 @@ public class GlobalExceptionHandler {
|
||||||
return ResponseEntity.status(status).body(errorResponse);
|
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.
|
* Handles validation errors from @Valid annotations.
|
||||||
*
|
*
|
||||||
|
|
@ -325,7 +345,7 @@ public class GlobalExceptionHandler {
|
||||||
RuntimeException ex,
|
RuntimeException ex,
|
||||||
HttpServletRequest request
|
HttpServletRequest request
|
||||||
) {
|
) {
|
||||||
logger.error("Unexpected error: {}", ex.getMessage(), ex);
|
logger.error("Unexpected runtime error: {}", ex.getMessage(), ex);
|
||||||
|
|
||||||
ErrorResponse errorResponse = ErrorResponse.from(
|
ErrorResponse errorResponse = ErrorResponse.from(
|
||||||
"INTERNAL_ERROR",
|
"INTERNAL_ERROR",
|
||||||
|
|
@ -338,4 +358,31 @@ public class GlobalExceptionHandler {
|
||||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body(errorResponse);
|
.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