From 6eafa7b33e87f0939f7ffe2e2c3bd70f81acabee Mon Sep 17 00:00:00 2001 From: piyushkag Date: Wed, 20 Nov 2024 12:03:09 +0530 Subject: [PATCH] Added logging mechanism for user actions. --- .../config/CachedBodyFilter.java | 26 +++ .../config/CachedBodyHttpServletRequest.java | 76 +++++++++ .../config/RequestCachingFilter.java | 25 +++ .../config/UniqueSessionInterceptor.java | 29 ++++ .../config/jwt/TokenProvider.java | 47 ++++-- .../constants/GepafinConstant.java | 6 + .../gepafin/tendermanagement/dao/CallDao.java | 32 +++- .../gepafin/tendermanagement/dao/FaqDao.java | 27 +++- .../tendermanagement/dao/LookUpDataDao.java | 43 +++-- .../gepafin/tendermanagement/dao/UserDao.java | 5 +- .../entities/UserActionEntity.java | 43 +++++ .../entities/VersionHistoryEntity.java | 33 ++++ .../enums/UserActionContextEnum.java | 20 +++ .../enums/UserActionLogsEnum.java | 20 +++ .../enums/VersionActionTypeEnum.java | 20 +++ .../model/request/UserActionRequest.java | 12 ++ .../model/request/VersionHistoryRequest.java | 17 ++ .../model/util/LogResponse.java | 28 ++++ .../repositories/UserActionsRepository.java | 9 ++ .../VersionHistoryRepository.java | 9 ++ .../service/impl/AuthenticationService.java | 10 +- .../tendermanagement/util/IpAddressUtils.java | 45 ++++++ .../tendermanagement/util/LoggingUtil.java | 149 ++++++++++++++++++ .../gepafin/tendermanagement/util/Utils.java | 41 ++++- .../web/rest/api/impl/CallApiController.java | 21 ++- .../db/changelog/db.changelog-1.0.0.xml | 47 ++++++ 26 files changed, 798 insertions(+), 42 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/config/CachedBodyFilter.java create mode 100644 src/main/java/net/gepafin/tendermanagement/config/CachedBodyHttpServletRequest.java create mode 100644 src/main/java/net/gepafin/tendermanagement/config/RequestCachingFilter.java create mode 100644 src/main/java/net/gepafin/tendermanagement/config/UniqueSessionInterceptor.java create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/request/VersionHistoryRequest.java create mode 100644 src/main/java/net/gepafin/tendermanagement/model/util/LogResponse.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java create mode 100644 src/main/java/net/gepafin/tendermanagement/util/IpAddressUtils.java create mode 100644 src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java diff --git a/src/main/java/net/gepafin/tendermanagement/config/CachedBodyFilter.java b/src/main/java/net/gepafin/tendermanagement/config/CachedBodyFilter.java new file mode 100644 index 00000000..c164dc60 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/config/CachedBodyFilter.java @@ -0,0 +1,26 @@ +package net.gepafin.tendermanagement.config; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class CachedBodyFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + if (request instanceof HttpServletRequest httpServletRequest) { + CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpServletRequest); + chain.doFilter(cachedRequest, response); + } else { + chain.doFilter(request, response); + } + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/config/CachedBodyHttpServletRequest.java b/src/main/java/net/gepafin/tendermanagement/config/CachedBodyHttpServletRequest.java new file mode 100644 index 00000000..f5c1582b --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/config/CachedBodyHttpServletRequest.java @@ -0,0 +1,76 @@ +package net.gepafin.tendermanagement.config; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { + + private final byte[] cachedBody; + + public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { + + super(request); + InputStream requestInputStream = request.getInputStream(); + this.cachedBody = requestInputStream.readAllBytes(); + } + + @Override + public ServletInputStream getInputStream() { + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + return new ServletInputStreamWrapper(byteArrayInputStream); + } + + @Override + public BufferedReader getReader() throws IOException { + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + return new BufferedReader(new InputStreamReader(byteArrayInputStream)); + } + + public String getCachedBodyAsString() { + + return new String(cachedBody); + } + + private static class ServletInputStreamWrapper extends ServletInputStream { + + private final ByteArrayInputStream byteArrayInputStream; + + public ServletInputStreamWrapper(ByteArrayInputStream byteArrayInputStream) { + + this.byteArrayInputStream = byteArrayInputStream; + } + + @Override + public int read() throws IOException { + + return byteArrayInputStream.read(); + } + + @Override + public boolean isFinished() { + + return byteArrayInputStream.available() == 0; + } + + @Override + public boolean isReady() { + + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/config/RequestCachingFilter.java b/src/main/java/net/gepafin/tendermanagement/config/RequestCachingFilter.java new file mode 100644 index 00000000..1ce8c840 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/config/RequestCachingFilter.java @@ -0,0 +1,25 @@ +package net.gepafin.tendermanagement.config; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RequestCachingFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (request instanceof HttpServletRequest) { + HttpServletRequest cachedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request); + chain.doFilter(cachedRequest, response); + } else { + chain.doFilter(request, response); + } + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/config/UniqueSessionInterceptor.java b/src/main/java/net/gepafin/tendermanagement/config/UniqueSessionInterceptor.java new file mode 100644 index 00000000..9f438d91 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/config/UniqueSessionInterceptor.java @@ -0,0 +1,29 @@ +package net.gepafin.tendermanagement.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class UniqueSessionInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + + request.getSession(true); + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + + if ("/v1/user/logout".equals(request.getRequestURI())) { + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java b/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java index 884c848f..41d9fc16 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java +++ b/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java @@ -6,6 +6,7 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.UserEntity; @@ -15,6 +16,7 @@ import net.gepafin.tendermanagement.web.rest.api.errors.Status; import net.gepafin.tendermanagement.web.rest.api.errors.UnauthorizedAccessException; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.time.DateUtils; +import org.apache.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -23,6 +25,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; @@ -49,9 +52,11 @@ public class TokenProvider { @Autowired private UserRepository userRepository; + @Autowired + HttpServletResponse response; + private SecretKey key; - private static final String AUTHORITIES_KEY = "auth"; private static final String MERCHANTID="merchantId"; static final String AUTH_SECRET = "X-Api-Secret"; @@ -82,11 +87,11 @@ public class TokenProvider { log.info("JWT Secret Key initialized."); } - public String createToken(Boolean rememberMe, UserEntity user) { -// String authorities = authentication.getAuthorities().stream() -// .map(GrantedAuthority::getAuthority) -// .collect(Collectors.joining(",")); - String authorities = user.getRoleEntity().getRoleType(); + public String createToken(Boolean rememberMe, UserEntity user, Long loginAttemptId) { + // String authorities = authentication.getAuthorities().stream() + // .map(GrantedAuthority::getAuthority) + // .collect(Collectors.joining(",")); + String authorities = user.getRoleEntity().getRoleType(); Long now; Date validity; @@ -103,19 +108,19 @@ public class TokenProvider { String payload = user.getEmail(); if(user != null) { payload += ":"+user.getId(); - } - - if(user != null) { payload += ":"+user.getHub().getId(); } String token = Jwts.builder() .setSubject(payload) - .claim("auth", authorities) + .claim(GepafinConstant.LOGIN_ATTEMPT_ID, loginAttemptId) + .claim(GepafinConstant.USER_ID, user.getId() != null ? user.getId() : null) + .claim(GepafinConstant.AUTH, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(validity) .compact(); + response.setHeader("Authorization", "Bearer " + token); log.debug("Generated token: {}", token); return token; } @@ -249,4 +254,26 @@ public class TokenProvider { return null; // Return null if token is not found or not in Bearer format } + public Claims getClaimsFromToken(String token) { + + return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); + } + + public String getCurrentActiveUserEmail() { + + var authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + String email = userDetails.getUsername(); + int lastColonIndex = email.lastIndexOf(":"); + int secondLastColonIndex = email.lastIndexOf(":", lastColonIndex - 1); + + if (secondLastColonIndex != -1) { + return email.substring(0, secondLastColonIndex); + } else { + return email; + } + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index b1704c6a..d1f65647 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -292,5 +292,11 @@ public class GepafinConstant { public static final String ENCRYPT_KEY = "U2VjdXJlRW5jcnlwdEtleQ=="; public static final String APPLICATION_DOCUMENTS_NOT_FOUND_MSG = "application.documents.not.found"; public static final String DUPLICATE_BENEFICIARY_CALL = "beneficiary.call.duplicate"; + public static final String AUTH = "auth"; + + //Logging + public static final String USER_ID = "userId"; + public static final String LOGIN_ATTEMPT_ID = "loginAttemptId"; + public static final String USER_ACTION_ID = "userActionId"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java index fe692b69..6aa766e1 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CallDao.java @@ -15,10 +15,13 @@ import java.util.zip.ZipOutputStream; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.*; import net.gepafin.tendermanagement.repositories.*; import net.gepafin.tendermanagement.service.*; import net.gepafin.tendermanagement.util.DateTimeUtil; +import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import org.h2.util.IOUtils; @@ -95,6 +98,11 @@ public class CallDao { @Autowired private Validator validator; + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private HttpServletRequest request; public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) { createCallRequest.setRegionId(userEntity.getRoleEntity().getRegion().getId()); @@ -144,7 +152,8 @@ public class CallDao { public CallEntity convertToCallEntity(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) { CallEntity callEntity = new CallEntity(); -// validateCallEntity(createCallRequest); + VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest(); + // validateCallEntity(createCallRequest); RegionEntity region = regionRepository.findById(createCallRequest.getRegionId()) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.REGION_NOT_FOUND))); @@ -180,6 +189,11 @@ public class CallDao { callEntity.setEndTime(DateTimeUtil.parseTime(createCallRequest.getEndTime())); callEntity.setHub(userEntity.getHub()); callEntity = callRepository.save(callEntity); + versionHistoryRequest.setOldData(null); + versionHistoryRequest.setNewData(callEntity); + versionHistoryRequest.setRequest(request); + versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); + loggingUtil.addVersionHistory(versionHistoryRequest); return callEntity; } @@ -197,7 +211,15 @@ public class CallDao { List evaluationCriteriaEntities = criteriaReqList.stream() .map(req -> convertToEvaluationCriteriaEntity(req, callEntity, type)).collect(Collectors.toList()); - evaluationCriteriaRepository.saveAll(evaluationCriteriaEntities); + List data = evaluationCriteriaRepository.saveAll(evaluationCriteriaEntities); + data.forEach(entity -> { + VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest(); + versionHistoryRequest.setOldData(null); + versionHistoryRequest.setNewData(entity); + versionHistoryRequest.setRequest(request); + versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); + loggingUtil.addVersionHistory(versionHistoryRequest); + }); return evaluationCriteriaEntities; } @@ -394,6 +416,7 @@ public class CallDao { private List createCallTargetAudienceCheckList(CallEntity callEntity, List lookUpDataEntities) { List lookUpDataResponses = new ArrayList<>(); + VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest(); for (LookUpDataEntity lookUpDataEntity : lookUpDataEntities) { CallTargetAudienceChecklistEntity callTargetAudienceChecklistEntity = new CallTargetAudienceChecklistEntity(); callTargetAudienceChecklistEntity.setIsValidated(false); @@ -402,6 +425,11 @@ public class CallDao { callTargetAudienceChecklistEntity.setIsDeleted(false); callTargetAudienceChecklistEntity = callTargetAudienceChecklistRepository .save(callTargetAudienceChecklistEntity); + versionHistoryRequest.setOldData(null); + versionHistoryRequest.setNewData(callTargetAudienceChecklistEntity); + versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); + versionHistoryRequest.setRequest(request); + loggingUtil.addVersionHistory(versionHistoryRequest); lookUpDataResponses.add(convertToLookUpDataResponseBean(callTargetAudienceChecklistEntity)); } return lookUpDataResponses; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/FaqDao.java b/src/main/java/net/gepafin/tendermanagement/dao/FaqDao.java index cfec2fe2..fb6050da 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/FaqDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/FaqDao.java @@ -1,20 +1,24 @@ package net.gepafin.tendermanagement.dao; +import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.CallEntity; import net.gepafin.tendermanagement.entities.FaqEntity; -import net.gepafin.tendermanagement.entities.LookUpDataEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.RoleStatusEnum; import net.gepafin.tendermanagement.entities.LookUpDataEntity.LookUpDataTypeEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.FaqReq; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.FaqResponseBean; import net.gepafin.tendermanagement.repositories.FaqRepository; import net.gepafin.tendermanagement.service.CallService; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.LookUpDataService; import net.gepafin.tendermanagement.util.DateTimeUtil; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; @@ -45,10 +49,16 @@ public class FaqDao { @Autowired private CompanyService companyService; + @Autowired + HttpServletRequest request; + + @Autowired + LoggingUtil loggingUtil; + public FaqResponseBean createFaq(FaqReq faqRequest, UserEntity userEntity, Long callId, Long companyId) { CallEntity callEntity = callService.validateCall(callId); FaqEntity entity = createOrUpdateFaqEntity(faqRequest, callEntity, userEntity, - LookUpDataEntity.LookUpDataTypeEnum.FAQ); + LookUpDataTypeEnum.FAQ); if (validator.checkIsBeneficiary() && companyId == null) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.COMPANY_ID_MANDATORY)); @@ -68,7 +78,7 @@ public class FaqDao { public FaqResponseBean updateFaq(Long id, FaqReq faqRequest, UserEntity userEntity) { FaqEntity entity = validateFaq(id); faqRequest.setId(entity.getId()); - createOrUpdateFaqEntity(faqRequest, entity.getCall(), userEntity, LookUpDataEntity.LookUpDataTypeEnum.FAQ); + createOrUpdateFaqEntity(faqRequest, entity.getCall(), userEntity, LookUpDataTypeEnum.FAQ); return convertToFaqResponseBean(entity); } @@ -93,10 +103,13 @@ public class FaqDao { public FaqEntity createOrUpdateFaqEntity(FaqReq faqReq, CallEntity callEntity, UserEntity userEntity, LookUpDataTypeEnum type) { FaqEntity faqEntity = null; + FaqEntity oldFaqData = null; + VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest(); if (isExistingFaq(faqReq)) { faqEntity = faqRepository.findByIdAndCallIdAndIsDeletedFalse(faqReq.getId(), callEntity.getId()) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.FAQ_NOT_FOUND))); + oldFaqData = Utils.getClonedEntityForData(faqEntity); } else { if (Boolean.FALSE.equals(userEntity.getRoleEntity().getRoleType().equals(RoleStatusEnum.ROLE_BENEFICIARY.getValue()))) { lookUpDataService.getOrCreateLookUpDataEntity(faqReq, type); @@ -115,7 +128,13 @@ public class FaqDao { setIfUpdated(faqEntity::getValue, faqEntity::setValue, faqReq.getValue()); setIfUpdated(faqEntity::getResponse, faqEntity::setResponse, faqReq.getResponse()); setIfUpdated(faqEntity::getIsVisible, faqEntity::setIsVisible, faqReq.getIsVisible()); - return faqRepository.save(faqEntity); + FaqEntity newFaqData = faqRepository.save(faqEntity); + versionHistoryRequest.setOldData(oldFaqData); + versionHistoryRequest.setNewData(newFaqData); + versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); + versionHistoryRequest.setRequest(request); + loggingUtil.addVersionHistory(versionHistoryRequest); + return newFaqData; } private boolean isExistingFaq(FaqReq faqReq) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/LookUpDataDao.java b/src/main/java/net/gepafin/tendermanagement/dao/LookUpDataDao.java index 1a31c306..f9a7f4e2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/LookUpDataDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/LookUpDataDao.java @@ -1,11 +1,17 @@ package net.gepafin.tendermanagement.dao; +import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.entities.LookUpDataEntity; import net.gepafin.tendermanagement.entities.LookUpDataEntity.LookUpDataTypeEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.LookUpDataReq; import net.gepafin.tendermanagement.model.request.LookUpDataRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.LookUpDataResponseBean; import net.gepafin.tendermanagement.repositories.LookUpDataRepository; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.util.Utils; 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; @@ -25,6 +31,12 @@ public class LookUpDataDao { @Autowired private LookUpDataRepository lookUpDataRepository; + @Autowired + HttpServletRequest request; + + @Autowired + LoggingUtil loggingUtil; + public LookUpDataResponseBean createLookUpData(LookUpDataRequest lookUpDataReq) { LookUpDataEntity entity = convertLookUpDataReqToLookUpDataEntity(lookUpDataReq); return convertLookUpDataEntityToResponseBean(entity); @@ -87,18 +99,25 @@ public class LookUpDataDao { .map(this::convertLookUpDataEntityToResponseBean) .collect(Collectors.toList()); } - - public LookUpDataEntity getOrCreateLookUpDataEntity(LookUpDataReq req, - LookUpDataEntity.LookUpDataTypeEnum type) { - if (req.getLookUpDataId() == null || req.getLookUpDataId().equals(0l)) { - LookUpDataEntity newEntity = new LookUpDataEntity(); - newEntity.setTitle(req.getTitle()); - newEntity.setValue(req.getValue()); - newEntity.setResponse(req.getResponse()); - newEntity.setType(type.getValue()); - validateLookUpDataEntity(newEntity); - return lookUpDataRepository.save(newEntity); - } + + public LookUpDataEntity getOrCreateLookUpDataEntity(LookUpDataReq req, LookUpDataEntity.LookUpDataTypeEnum type) { + + if (req.getLookUpDataId() == null || req.getLookUpDataId().equals(0L)) { + LookUpDataEntity newEntity = new LookUpDataEntity(); + VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest(); + newEntity.setTitle(req.getTitle()); + newEntity.setValue(req.getValue()); + newEntity.setResponse(req.getResponse()); + newEntity.setType(type.getValue()); + validateLookUpDataEntity(newEntity); + LookUpDataEntity lookUpDataEntity = lookUpDataRepository.save(newEntity); + versionHistoryRequest.setOldData(null); + versionHistoryRequest.setNewData(lookUpDataEntity); + versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT); + versionHistoryRequest.setRequest(request); + loggingUtil.addVersionHistory(versionHistoryRequest); + return lookUpDataEntity; + } return lookUpDataRepository.findById(req.getLookUpDataId()) .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, diff --git a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java index 53d21fc5..d5cda645 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java @@ -94,13 +94,14 @@ public class UserDao { log.info("User created with ID: {}", userEntity.getId()); LoginReq loginReq=new LoginReq(); loginReq.setEmail(userEntity.getEmail()); + LoginAttemptEntity loginAttemptEntity = null; if(userEntity!=null){ - LoginAttemptEntity loginAttemptEntity =authenticationService.prepareLoginAttemptEntity(loginReq, request); + loginAttemptEntity = authenticationService.prepareLoginAttemptEntity(loginReq, request); log.info("Authentication failed for email: {}", loginReq.getEmail()); loginAttemptEntity.setUserId(userEntity.getId()); authenticationService.createSuccessLoginAttempt(loginAttemptEntity); } - return authService.getJWTTokenBean(userEntity, Boolean.TRUE); + return authService.getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId()); } private BeneficiaryEntity createBeneficiary(RoleEntity roleEntity, UserReq userReq, HubEntity hub) { diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java new file mode 100644 index 00000000..019f0317 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java @@ -0,0 +1,43 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name = "user_action") +public class UserActionEntity extends BaseEntity { + + @Column(name = "USER_ID") + private Long userId; + + @Column(name = "ACTION_TYPE") + private String actionType; + + @Column(name = "REQUEST_BODY") + private String requestBody; + + @Column(name = "LOGIN_ATTEMPT_ID") + private Long loginAttemptId; + + @Column(name = "IP_ADDRESS") + private String ipAddress; + + @Column(name = "METHOD_TYPE") + private String methodType; + + @Column(name = "HUB_ID") + private Long hubId; + + @Column(name = "URL") + private String url; + + @Column(name = "ACTION_CONTEXT") + private String actionContext; + + @Column(name = "RESPONSE") + private Object response; + +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java new file mode 100644 index 00000000..7a544363 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/VersionHistoryEntity.java @@ -0,0 +1,33 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name = "version_history") +public class VersionHistoryEntity extends BaseEntity { + + @Column(name = "OLD_DATA", columnDefinition = "LONGTEXT") + private String oldData; + + @Column(name = "NEW_DATA", columnDefinition = "LONGTEXT") + private String newData; + + @Column(name = "TABLE_NAME") + private String tableName; + + @Column(name = "ACTION_TYPE") + private String actionType; + + @Column(name = "RECORD_ID") + private Long recordId; + + @Column(name = "USER_ACTION_ID") + private Long userActionId; + + @Column(name = "USER_ID") + private Long userId; +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java new file mode 100644 index 00000000..be56c433 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum UserActionContextEnum { + CREATE_CALL("CREATE_CALL"),; + + private final String value; + + UserActionContextEnum(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java new file mode 100644 index 00000000..65f2243d --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum UserActionLogsEnum { + LOGIN("LOGIN"), LOGOUT("LOGOUT"), UPDATE("UPDATE"), DELETE("DELETE"), VIEW("VIEW"), INSERT("INSERT"); + + private final String value; + + UserActionLogsEnum(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java new file mode 100644 index 00000000..27d2a55d --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java @@ -0,0 +1,20 @@ +package net.gepafin.tendermanagement.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum VersionActionTypeEnum { + UPDATE("UPDATE"), DELETE("DELETE"), SOFT_DELETE("SOFT_DELETE"), INSERT("INSERT"); + + private final String value; + + VersionActionTypeEnum(String value) { + + this.value = value; + } + + @JsonValue + public String getValue() { + + return value; + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java new file mode 100644 index 00000000..0b28f70b --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java @@ -0,0 +1,12 @@ +package net.gepafin.tendermanagement.model.request; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; + +@Data +public class UserActionRequest { + private HttpServletRequest request; + private UserActionLogsEnum actionType; + private String actionContext; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/VersionHistoryRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/VersionHistoryRequest.java new file mode 100644 index 00000000..c09bad29 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/VersionHistoryRequest.java @@ -0,0 +1,17 @@ +package net.gepafin.tendermanagement.model.request; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import net.gepafin.tendermanagement.entities.BaseEntity; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; + +@Data +public class VersionHistoryRequest { + private BaseEntity oldData; + private BaseEntity newData; + private VersionActionTypeEnum actionType; + private HttpServletRequest request; + private Long userActionId; + private Long recordId; + private String tableName; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/util/LogResponse.java b/src/main/java/net/gepafin/tendermanagement/model/util/LogResponse.java new file mode 100644 index 00000000..e7b26b04 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/util/LogResponse.java @@ -0,0 +1,28 @@ +package net.gepafin.tendermanagement.model.util; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.gepafin.tendermanagement.web.rest.api.errors.Status; + +import java.io.Serial; +import java.io.Serializable; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class LogResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Integer status; + + private String statusCode; + + private String message; +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java new file mode 100644 index 00000000..ba651c6f --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java @@ -0,0 +1,9 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.UserActionEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserActionsRepository extends JpaRepository { +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java new file mode 100644 index 00000000..059024ab --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java @@ -0,0 +1,9 @@ +package net.gepafin.tendermanagement.repositories; + +import net.gepafin.tendermanagement.entities.VersionHistoryEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface VersionHistoryRepository extends JpaRepository { +} diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java index ddd74035..30edb908 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java @@ -103,7 +103,7 @@ public class AuthenticationService { createFailedLoginAttempt(loginAttemptEntity, e.getMessage()); throw e; } - return getJWTTokenBean(user, loginReq.getRememberMe()); + return getJWTTokenBean(user, loginReq.getRememberMe(), loginAttemptEntity.getId()); } public LoginAttemptEntity prepareLoginAttemptEntity(LoginReq loginUserReq, HttpServletRequest request) { @@ -126,10 +126,10 @@ public class AuthenticationService { loginAttemptEntity.setErrorMsg(errorMsg); loginAttemptDao.createLoginAttempt(loginAttemptEntity); } - public JWTToken getJWTTokenBean(UserEntity user, Boolean rememberMe) { + public JWTToken getJWTTokenBean(UserEntity user, Boolean rememberMe, Long loginAttemptId) { user.setLastLogin(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); - userRepository.save(user); - String token = tokenProvider.createToken(rememberMe, user); + user = userRepository.save(user); + String token = tokenProvider.createToken(rememberMe, user, loginAttemptId); log.info("JWT token generated for email: {}", user.getEmail()); RoleResponseBean roleResponseBean = roleDao.convertRoleEntityToRoleResponse(user.getRoleEntity()); @@ -215,7 +215,7 @@ public class AuthenticationService { loginReq.setEmail(userEntity.getEmail()); loginAttemptEntity = prepareLoginAttemptEntity(loginReq, request); loginAttemptEntity.setUserId(userEntity.getId()); - return getJWTTokenBean(userEntity, Boolean.TRUE); + return getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId()); } catch (Exception e) { log.info("Authentication login failed for email: {}",e.getMessage()); loginAttemptEntity.setUserId(userId); diff --git a/src/main/java/net/gepafin/tendermanagement/util/IpAddressUtils.java b/src/main/java/net/gepafin/tendermanagement/util/IpAddressUtils.java new file mode 100644 index 00000000..e4673039 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/util/IpAddressUtils.java @@ -0,0 +1,45 @@ +package net.gepafin.tendermanagement.util; + +import jakarta.servlet.http.HttpServletRequest; + +import java.util.regex.Pattern; + +public class IpAddressUtils { + + private static final Pattern IPV4_PATTERN = Pattern.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); + + private static final Pattern IPV6_PATTERN = Pattern.compile( + "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|" + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + "::(ffff(:0{1,4}){0,1}:){0,1}(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3,3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|([0-9a-fA-F]{1,4}:){1,4}:" + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3,3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"); + + public static String getClientIp(HttpServletRequest request) { + + String ipAddress = null; + + String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" }; + + for (String header : headers) { + ipAddress = request.getHeader(header); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = ipAddress.split(",")[0].trim(); + break; + } + } + if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getRemoteAddr(); + } + if ("0:0:0:0:0:0:0:1".equals(ipAddress) || "127.0.0.1".equals(ipAddress)) { + ipAddress = "127.0.0.1"; + } + if (isValidIpAddress(ipAddress)) { + return ipAddress; + } else { + return "Invalid IP"; + } + } + + private static boolean isValidIpAddress(String ipAddress) { + + return IPV4_PATTERN.matcher(ipAddress).matches() || IPV6_PATTERN.matcher(ipAddress).matches(); + } +} + diff --git a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java new file mode 100644 index 00000000..2fa50b3a --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java @@ -0,0 +1,149 @@ +package net.gepafin.tendermanagement.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import jakarta.persistence.Table; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.gepafin.tendermanagement.config.CachedBodyHttpServletRequest; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; +import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.UserActionEntity; +import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.entities.VersionHistoryEntity; +import net.gepafin.tendermanagement.model.request.UserActionRequest; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; +import net.gepafin.tendermanagement.model.util.LogResponse; +import net.gepafin.tendermanagement.repositories.UserActionsRepository; +import net.gepafin.tendermanagement.repositories.VersionHistoryRepository; +import net.gepafin.tendermanagement.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.annotation.RequestScope; + +@Slf4j +@Component +@RequestScope +public class LoggingUtil { + + @Autowired + private UserActionsRepository userActionsRepository; + + @Autowired + private VersionHistoryRepository versionHistoryRepository; + + @Autowired + private UserService userService; + + @Autowired + TokenProvider tokenProvider; + + public UserActionEntity logUserAction(UserActionRequest userActionRequest) { + UserActionEntity userAction = new UserActionEntity(); + try { + String token = tokenProvider.extractTokenFromRequest(userActionRequest.getRequest()); + Claims claims = tokenProvider.getClaimsFromToken(token); + Long userId = claims.get(GepafinConstant.USER_ID, Long.class); + Long loginAttemptId = claims.get(GepafinConstant.LOGIN_ATTEMPT_ID, Long.class); + UserEntity userEntity = userService.validateUser(userId); + + String requestBody = null; + if (userActionRequest.getRequest() instanceof CachedBodyHttpServletRequest cachedRequest) { + requestBody = cachedRequest.getCachedBodyAsString(); + } + userAction.setActionType(userActionRequest.getActionType().getValue()); + userAction.setUserId(userId); + userAction.setActionContext(userActionRequest.getActionContext()); + userAction.setMethodType(userActionRequest.getRequest().getMethod()); + userAction.setHubId(userEntity.getHub().getId()); + userAction.setUrl(userActionRequest.getRequest().getRequestURI()); + userAction.setLoginAttemptId(loginAttemptId); + userAction.setIpAddress(IpAddressUtils.getClientIp(userActionRequest.getRequest())); + userAction.setRequestBody(requestBody); + userAction.setResponse(null); + userActionsRepository.save(userAction); + userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId()); + } catch (Exception e) { + log.error("Error logging user action: {}", e.getMessage(), e); + } + return userAction; + } + +// @Transactional +// public UserActionEntity updateUserAction(HttpServletResponse response, UserActionEntity userAction) { +// try { +// String requestBody = null; +// if (userAction. instanceof CachedBodyHttpServletRequest cachedRequest) { +// requestBody = cachedRequest.getCachedBodyAsString(); +// } +// +// LogResponse logResponse = new LogResponse<>(); +// logResponse.setStatus(response.getStatus()); +// logResponse.setStatusCode(String.valueOf(response.getStatus())); +// logResponse.setMessage("SUCCESS"); +// +// String serializedResponse; +// try { +// ObjectMapper objectMapper = new ObjectMapper(); +// serializedResponse = objectMapper.writeValueAsString(logResponse); +// } catch (Exception e) { +// serializedResponse = "Error serializing LogResponse: " + e.getMessage(); +// } +// +// userAction.setResponse(serializedResponse); +// userActionsRepository.save(userAction); +// } catch (Exception e) { +// log.error("Error updating user action: {}", e.getMessage(), e); +// } +// return userAction; +// } + + public void logVersionHistory(VersionHistoryRequest versionHistoryRequest) { + try { + VersionHistoryEntity history = new VersionHistoryEntity(); + String token = tokenProvider.extractTokenFromRequest(versionHistoryRequest.getRequest()); + Claims claims = tokenProvider.getClaimsFromToken(token); + Long userId = claims.get(GepafinConstant.USER_ID, Long.class); + String oldData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getOldData()); + String newData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getNewData()); + history.setUserActionId(versionHistoryRequest.getUserActionId()); + history.setActionType(versionHistoryRequest.getActionType().getValue()); + history.setOldData(oldData); + history.setNewData(newData); + history.setRecordId(versionHistoryRequest.getRecordId()); + history.setTableName(versionHistoryRequest.getTableName()); + history.setUserId(userId); + versionHistoryRepository.save(history); + } catch (Exception e) { + log.error("Error logging version history: {}", e.getMessage(), e); + } + } + + public String getTableName(Class entityClass) { + try { + if (entityClass.isAnnotationPresent(Table.class)) { + Table tableAnnotation = entityClass.getAnnotation(Table.class); + return tableAnnotation.name(); + } + } catch (Exception e) { + log.error("Error retrieving table name: {}", e.getMessage(), e); + } + return null; + } + + public void addVersionHistory(VersionHistoryRequest versionHistoryRequest) { + try { + Long userActionId = (Long) versionHistoryRequest.getRequest().getAttribute(GepafinConstant.USER_ACTION_ID); + Long recordId = versionHistoryRequest.getNewData().getId(); + String tableName = getTableName(versionHistoryRequest.getNewData().getClass()); + versionHistoryRequest.setRecordId(recordId); + versionHistoryRequest.setUserActionId(userActionId); + versionHistoryRequest.setTableName(tableName); + logVersionHistory(versionHistoryRequest); + } catch (Exception e) { + log.error("Error adding version history: {}", e.getMessage(), e); + } + } +} diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index 26f049ef..76f4fac6 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -5,20 +5,19 @@ import java.lang.reflect.Field; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; -import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; -import com.itextpdf.styledxmlparser.jsoup.Jsoup; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.SerializationFeature; import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.constants.GepafinConstant; import org.apache.commons.collections4.MapUtils; @@ -515,4 +514,36 @@ public class Utils { } return null; } + + public static String convertEntityToJsonForLogging(Object entity) { + + if(entity == null){ + return null; + } + try { + return mapper.writeValueAsString(entity); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return null; + } + } + + @JsonIgnoreProperties({ "childEntities", "relatedData", "otherRelations" }) + private abstract static class IgnoreChildEntities { + } + + public static T getClonedEntityForData(T entity) { + + if (entity == null) { + return null; + } + try { + String json = mapper.writeValueAsString(entity); + @SuppressWarnings("unchecked") T clonedEntity = (T) mapper.readValue(json, entity.getClass()); + return clonedEntity; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to clone entity", e); + } + } } diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java index 32c89055..40b3679f 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/CallApiController.java @@ -2,7 +2,13 @@ package net.gepafin.tendermanagement.web.rest.api.impl; import java.util.List; +import lombok.extern.slf4j.Slf4j; import net.gepafin.tendermanagement.enums.CallStatusEnum; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; +import net.gepafin.tendermanagement.model.request.UserActionRequest; +import net.gepafin.tendermanagement.repositories.UserActionsRepository; +import net.gepafin.tendermanagement.util.LoggingUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -33,11 +39,22 @@ public class CallApiController implements CallApi { @Autowired private CallService callService; + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private UserActionsRepository userActionsRepository; @Override - @Transactional(rollbackFor=Exception.class) + @Transactional(rollbackFor = Exception.class) public ResponseEntity> createCallStep1(HttpServletRequest request, CreateCallRequestStep1 createCallRequest) { - CallResponse createCallResponseBean = callService.createCallStep1(request, createCallRequest); + + UserActionRequest userActionRequest = new UserActionRequest(); + userActionRequest.setRequest(request); + userActionRequest.setActionType(UserActionLogsEnum.INSERT); + userActionRequest.setActionContext(UserActionContextEnum.CREATE_CALL.getValue()); + loggingUtil.logUserAction(userActionRequest); + CallResponse createCallResponseBean = callService.createCallStep1(request, createCallRequest); return ResponseEntity.status(HttpStatus.CREATED) .body(new Response<>(createCallResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.CALL_CREATED_SUCCESSFULLY_MSG))); } diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 15dad70f..b4c88aa2 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -1732,4 +1732,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +