diff --git a/pom.xml b/pom.xml
index c463be91..02b5fb51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -245,6 +245,12 @@
reactor-netty
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.15.2
+
+
diff --git a/src/main/java/net/gepafin/tendermanagement/config/JacksonConfig.java b/src/main/java/net/gepafin/tendermanagement/config/JacksonConfig.java
new file mode 100644
index 00000000..51ad3191
--- /dev/null
+++ b/src/main/java/net/gepafin/tendermanagement/config/JacksonConfig.java
@@ -0,0 +1,20 @@
+package net.gepafin.tendermanagement.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JacksonConfig {
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ return mapper;
+ }
+}
+
diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
index 42853112..5869fc81 100644
--- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
+++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java
@@ -355,5 +355,10 @@ public class GepafinConstant {
public static final String NOTIFICATION_DELETED_SUCCESSFULLY="notification.deleted.successfully";
public static final String NOTIFICATION_UPDATED_SUCCESSFULLY="notification.updated.successfully";
public static final String USER_WITH_COMPANY_NOT_FOUND = "user.with.company.not.found";
+
+ //action log response
+ public static final String STATUS_CODE_STRING = "statusCode";
+ public static final String GET_STATUS_CODE_STRING = "status";
+ public static final String MESSAGE_STRING = "message";
}
diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java
index f0163c47..804fdb9b 100644
--- a/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java
+++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java
@@ -6,5 +6,5 @@ import org.springframework.stereotype.Repository;
@Repository
public interface UserActionsRepository extends JpaRepository {
- UserActionEntity findUserActionById(Long id);
+ UserActionEntity findUserActionByIdAndIsDeletedFalse(Long id);
}
diff --git a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java
index e2ab4521..90b7a052 100644
--- a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java
+++ b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java
@@ -41,6 +41,8 @@ public class LoggingUtil {
@Autowired
private TokenProvider tokenProvider;
+ private static final ThreadLocal userActionIdHolder = new ThreadLocal<>();
+
public UserActionEntity logUserAction(UserActionRequest userActionRequest) {
UserActionEntity userAction = new UserActionEntity();
try {
@@ -83,12 +85,22 @@ public class LoggingUtil {
userAction.setResponse(response);
userActionsRepository.save(userAction);
userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId());
+ userActionIdHolder.set(userAction.getId());
} catch (Exception e) {
log.error("Error logging user action: {}", e.getMessage(), e);
}
return userAction;
}
+ public Long getUserActionId() {
+ return userActionIdHolder.get();
+ }
+
+ public void clearUserActionId() {
+ userActionIdHolder.remove();
+ log.info("UserActionId cleared from ThreadLocal");
+ }
+
private String normalizeUrl(String url) {
url = url.replaceAll("(? getVersionHistoryLogById(Long id) {
return versionHistoryRepository.findVersionHistoryByUserActionId(id);
}
+
+ public void updateUserActionWithError(Long userActionId, String errorDetails) {
+ if (userActionId == null) {
+ return;
+ }
+
+ UserActionEntity userAction = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId);
+ if (userAction != null) {
+ userAction.setResponse(errorDetails);
+ userActionsRepository.save(userAction);
+ }
+ }
+
+ public void updateUserActionWithResponse(Long userActionId, String response) {
+ if (userActionId == null) {
+ return;
+ }
+
+ UserActionEntity userAction = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId);
+ if (userAction != null) {
+ userAction.setResponse(response);
+ userActionsRepository.save(userAction);
+ }
+ }
+
}
diff --git a/src/main/java/net/gepafin/tendermanagement/util/UserActionAspect.java b/src/main/java/net/gepafin/tendermanagement/util/UserActionAspect.java
new file mode 100644
index 00000000..0064bb82
--- /dev/null
+++ b/src/main/java/net/gepafin/tendermanagement/util/UserActionAspect.java
@@ -0,0 +1,148 @@
+package net.gepafin.tendermanagement.util;
+
+import com.amazonaws.services.alexaforbusiness.model.UnauthorizedException;
+import jakarta.persistence.EntityNotFoundException;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import net.gepafin.tendermanagement.constants.GepafinConstant;
+import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
+import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException;
+import net.gepafin.tendermanagement.web.rest.api.errors.Status;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.AccessDeniedException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Aspect
+@Component
+@Slf4j
+public class UserActionAspect {
+
+ @Autowired
+ private LoggingUtil loggingUtil;
+
+ @Around("execution(public * net.gepafin.tendermanagement.web.rest.api.impl..*(..))")
+ public Object logApiResponse(ProceedingJoinPoint joinPoint) throws Throwable {
+
+ Object result;
+
+ HttpServletRequest request = getRequestFromContext();
+ try {
+ Long userActionId = getUserActionIdFromRequest(request);
+
+ if (userActionId != null) {
+ request.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId);
+ log.info("Stored userActionId in RequestContext: {}", userActionId);
+ } else {
+ userActionId = loggingUtil.getUserActionId();
+ if (userActionId != null) {
+ request.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId);
+ }
+ }
+
+ result = joinPoint.proceed();
+
+ if (result instanceof ResponseEntity>) {
+ Long storedUserActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID);
+ handleSuccessResponse((ResponseEntity>) result, storedUserActionId == null ? userActionId : storedUserActionId);
+ }
+ } catch (Exception ex) {
+ log.error("Exception occurred: ", ex);
+ handleError(ex, getUserActionIdFromRequest(request));
+ throw ex;
+ } finally {
+ loggingUtil.clearUserActionId();
+ }
+
+ return result;
+ }
+
+ private void handleSuccessResponse(ResponseEntity> responseEntity, Long userActionId) {
+
+ if (userActionId != null) {
+ Map responseWithUserAction = new LinkedHashMap<>();
+ responseWithUserAction.put(GepafinConstant.STATUS_CODE_STRING, responseEntity.getStatusCode().value());
+
+ // Log and update user action
+ loggingUtil.updateUserActionWithResponse(userActionId, Utils.convertMapIntoJsonString(responseWithUserAction));
+ log.info("Updated userActionId with response: {}", userActionId);
+ }
+ }
+
+ private void handleError(Throwable ex, Long userActionId) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
+
+ HttpStatus status = getStatusCodeFromException(ex);
+ log.info("Status Code received from exception : {}", status);
+ String errorMessage = ex.getMessage();
+
+ Map errorResponse = new LinkedHashMap<>();
+ errorResponse.put(GepafinConstant.STATUS_CODE_STRING, status.value());
+ errorResponse.put(GepafinConstant.GET_STATUS_CODE_STRING, status);
+ errorResponse.put(GepafinConstant.MESSAGE_STRING, errorMessage);
+
+ if (userActionId != null) {
+ String errorDetails = Utils.convertMapIntoJsonString(errorResponse);
+ loggingUtil.updateUserActionWithError(userActionId, errorDetails);
+ log.info("Updated userActionId with error details: {}", userActionId);
+ }
+ }
+
+ private HttpServletRequest getRequestFromContext() {
+
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ return attributes != null ? attributes.getRequest() : null;
+ }
+
+ private Long getUserActionIdFromRequest(HttpServletRequest request) {
+
+ if (request != null) {
+ Object userActionIdAttr = request.getAttribute(GepafinConstant.USER_ACTION_ID);
+ return userActionIdAttr != null ? Long.valueOf(userActionIdAttr.toString()) : null;
+ }
+ return null;
+ }
+
+ private HttpStatus getStatusCodeFromException(Throwable ex) {
+
+ if (ex instanceof ResourceNotFoundException) {
+ return HttpStatus.NOT_FOUND;
+ }
+
+ if (ex instanceof ResponseStatusException responseStatusException) {
+ return (HttpStatus) responseStatusException.getStatusCode();
+ }
+
+ if (ex instanceof CustomValidationException) {
+ return HttpStatus.BAD_REQUEST;
+ }
+
+ if (ex instanceof EntityNotFoundException) {
+ return HttpStatus.NOT_FOUND;
+ }
+ if (ex instanceof IllegalArgumentException || ex instanceof MissingServletRequestParameterException || ex instanceof MethodArgumentNotValidException) {
+ return HttpStatus.BAD_REQUEST;
+ }
+ if (ex instanceof AccessDeniedException) {
+ return HttpStatus.FORBIDDEN;
+ }
+ if (ex instanceof UnauthorizedException) {
+ return HttpStatus.UNAUTHORIZED;
+ }
+
+ return HttpStatus.INTERNAL_SERVER_ERROR;
+ }
+}
diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java
index 40f95e3b..0770bce5 100644
--- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java
+++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java
@@ -154,6 +154,9 @@ public class Utils {
public static String convertMapIntoJsonString(Map map) {
try {
ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ mapper.enable(SerializationFeature.INDENT_OUTPUT);
if (MapUtils.isNotEmpty(map)) {
return mapper.writeValueAsString(map);
}