From dd148c0ebfb66d0ff00f2c469bd130f6e8f7af89 Mon Sep 17 00:00:00 2001 From: piyushkag Date: Fri, 22 Nov 2024 09:05:21 +0530 Subject: [PATCH] Added action logs for user api's. --- .../constants/GepafinConstant.java | 2 +- .../tendermanagement/dao/LoginAttemptDao.java | 30 +++- .../gepafin/tendermanagement/dao/UserDao.java | 56 ++++-- .../entities/LoginAttemptEntity.java | 13 +- .../entities/UserActionEntity.java | 2 +- .../enums/UserActionContextEnum.java | 15 +- .../enums/UserActionLogsEnum.java | 9 +- .../enums/VersionActionTypeEnum.java | 5 +- .../model/request/UserActionRequest.java | 2 + .../repositories/UserActionsRepository.java | 1 + .../VersionHistoryRepository.java | 7 + .../service/impl/AuthenticationService.java | 26 ++- .../service/impl/UserServiceImpl.java | 6 + .../tendermanagement/util/LoggingUtil.java | 166 +++++++++++++++++- .../gepafin/tendermanagement/util/Utils.java | 12 +- .../web/rest/api/impl/CallApiController.java | 2 +- .../web/rest/api/impl/UserApiController.java | 116 +++++++++--- .../db/changelog/db.changelog-1.0.0.xml | 7 + 18 files changed, 404 insertions(+), 73 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index d1f65647..516c15ed 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -156,7 +156,7 @@ public class GepafinConstant { public static final String IS_PIVA = "isPIVA"; public static final String FAILED_RETAIN_FIELD = "failed.retain.field"; public static final String USER_ALREADY_EXIST_MSG = "user.already.exist.msg"; - public static final String TOKEN_VALIDATE_SUCCESS_MSE = "token.validate.success"; + public static final String TOKEN_VALIDATE_SUCCESS_MSG = "token.validate.success"; public static final String INVALID_REQUEST = "invalid.request"; public static final String CODICE_FISCALE_EXISTS = "codice.fiscale.exists"; public static final String TOTAL_STEPS_NOT_BE_ZERO = "total.steps.not.zero"; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/LoginAttemptDao.java b/src/main/java/net/gepafin/tendermanagement/dao/LoginAttemptDao.java index 9ff64040..a4629584 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/LoginAttemptDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/LoginAttemptDao.java @@ -1,12 +1,17 @@ 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.LoginAttemptEntity; import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.LoginAttemptPageableResponseBean; import net.gepafin.tendermanagement.repositories.LoginAttemptRepository; import net.gepafin.tendermanagement.util.DateTimeUtil; +import net.gepafin.tendermanagement.util.LoggingUtil; +import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -24,10 +29,29 @@ public class LoginAttemptDao { @Autowired LoginAttemptRepository loginAttemptRepository; - public void createLoginAttempt(LoginAttemptEntity loginAttemptEntity) { - loginAttemptEntity.setAttemptDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + @Autowired + private LoggingUtil loggingUtil; - loginAttemptRepository.save(loginAttemptEntity); + @Autowired + private HttpServletRequest request; + + public LoginAttemptEntity createLoginAttempt(LoginAttemptEntity loginAttemptEntity) { + + VersionActionTypeEnum actionType; + LoginAttemptEntity oldLoginAttemptEntity = Utils.getClonedEntityForData(loginAttemptEntity); + loginAttemptEntity.setAttemptDate(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); + if(loginAttemptEntity.getId() != null ) { + actionType = VersionActionTypeEnum.UPDATE; + }else{ + actionType = VersionActionTypeEnum.INSERT; + oldLoginAttemptEntity = null; + } + loginAttemptEntity = loginAttemptRepository.save(loginAttemptEntity); + + /** This code is responsible for adding a version history log for "Create Login Attempt" operation. **/ + loggingUtil.addVersionHistoryWithoutToken(VersionHistoryRequest.builder().actionType(actionType).request(request).oldData(oldLoginAttemptEntity).newData(loginAttemptEntity).build()); + + return loginAttemptEntity; } public LoginAttemptPageableResponseBean> getLoginAttemptsList(UserEntity userEntity, Integer pageNo, Integer pageLimit) { diff --git a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java index d5cda645..da5c6f07 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java @@ -7,7 +7,10 @@ import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.RoleStatusEnum; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.CompanyResponse; import net.gepafin.tendermanagement.model.response.RoleResponseBean; @@ -19,6 +22,7 @@ import net.gepafin.tendermanagement.repositories.UserRepository; import net.gepafin.tendermanagement.service.HubService; import net.gepafin.tendermanagement.service.RoleService; import net.gepafin.tendermanagement.service.impl.AuthenticationService; +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; @@ -80,28 +84,43 @@ public class UserDao { @Autowired private AuthenticationService authenticationService; - + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private HttpServletRequest request; + public JWTToken createUser(HttpServletRequest request, String tempToken, UserReq userReq) { - if(StringUtils.isEmpty(userReq.getHubUuid())) { - userReq.setHubUuid(defaultHubUuid); - } - HubEntity hub = hubService.getHubByUuid(userReq.getHubUuid()); - validateUserRequest(request, tempToken, userReq, hub); - validatePassword(userReq.getPassword(), userReq.getConfPassword(), tempToken); - RoleEntity roleEntity = getRoleEntity(userReq.getRoleId()); - BeneficiaryEntity beneficiary = createBeneficiary(roleEntity, userReq, hub); + + if (StringUtils.isEmpty(userReq.getHubUuid())) { + userReq.setHubUuid(defaultHubUuid); + } + HubEntity hub = hubService.getHubByUuid(userReq.getHubUuid()); + validateUserRequest(request, tempToken, userReq, hub); + validatePassword(userReq.getPassword(), userReq.getConfPassword(), tempToken); + RoleEntity roleEntity = getRoleEntity(userReq.getRoleId()); + BeneficiaryEntity beneficiary = createBeneficiary(roleEntity, userReq, hub); UserEntity userEntity = convertUserRequestToUserEntity(beneficiary, roleEntity, userReq, hub); log.info("User created with ID: {}", userEntity.getId()); - LoginReq loginReq=new LoginReq(); + LoginReq loginReq = new LoginReq(); loginReq.setEmail(userEntity.getEmail()); LoginAttemptEntity loginAttemptEntity = null; - if(userEntity!=null){ + if (userEntity != null) { loginAttemptEntity = authenticationService.prepareLoginAttemptEntity(loginReq, request); log.info("Authentication failed for email: {}", loginReq.getEmail()); loginAttemptEntity.setUserId(userEntity.getId()); - authenticationService.createSuccessLoginAttempt(loginAttemptEntity); + authenticationService.createSuccessLoginAttempt(loginAttemptEntity); } - return authService.getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId()); + + JWTToken token = authService.getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId()); + + /** This code is responsible for adding a version history log for the "Create beneficiary" operation. **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).newData(beneficiary).build()); + + /** This code is responsible for adding a version history log for the "Create user" operation. **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).newData(userEntity).build()); + + return token; } private BeneficiaryEntity createBeneficiary(RoleEntity roleEntity, UserReq userReq, HubEntity hub) { @@ -196,6 +215,7 @@ public class UserDao { public UserResponseBean updateUser(Long userId, UpdateUserReq userReq) { log.info("Updating user with ID: {}", userId); UserEntity userEntity=validateUser(userId); + UserEntity oldUserEntity = Utils.getClonedEntityForData(userEntity); log.info("Current user details: {}", userEntity); log.info("New user details: {}", userReq); String newStatus = userReq.getStatus() != null ? userReq.getStatus().getValue() : null; @@ -218,6 +238,10 @@ public class UserDao { } setIfUpdated(userEntity.getBeneficiary()::getEmailPec, userEntity.getBeneficiary()::setEmailPec, userReq.getEmailPec()); userEntity = userRepository.save(userEntity); + + /** This code is responsible for adding a version history log for the "Update user details" operation **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(oldUserEntity).newData(userEntity).build()); + log.info("User updated with ID: {}", userEntity.getId()); return convertUserEntityToUserResponse(userEntity); } @@ -305,7 +329,11 @@ public class UserDao { public void deleteUser(Long id) { log.info("Deleting user with ID: {}", id); - validateUser(id); + UserEntity oldUserData = validateUser(id); + + /** This code is responsible for adding a version history log for the "Delete user" operation. **/ + loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.DELETE).oldData(oldUserData).build()); + userRepository.deleteById(id); log.info("User deleted with ID: {}", id); } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java index 11fe1a99..bc3e445e 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/LoginAttemptEntity.java @@ -1,21 +1,14 @@ package net.gepafin.tendermanagement.entities; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; import java.time.LocalDateTime; @Entity @Table(name = "LOGIN_ATTEMPT") -@Getter -@Setter -public class LoginAttemptEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ID", unique = true) - private Long id; +@Data +public class LoginAttemptEntity extends BaseEntity{ @Column(name = "USERNAME") private String username; diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java index 019f0317..488a6c71 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserActionEntity.java @@ -38,6 +38,6 @@ public class UserActionEntity extends BaseEntity { private String actionContext; @Column(name = "RESPONSE") - private Object response; + private String response; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java index 4b1f699c..e2ffcd99 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionContextEnum.java @@ -3,7 +3,7 @@ package net.gepafin.tendermanagement.enums; import com.fasterxml.jackson.annotation.JsonValue; public enum UserActionContextEnum { - + /** call action context **/ CREATE_CALL_STEP_1("CREATE_CALL_STEP_1"), UPDATE_CALL_STEP_1("UPDATE_CALL_STEP_1"), @@ -12,10 +12,17 @@ public enum UserActionContextEnum { UPDATE_CALL_STATUS("UPDATE_CALL_STATUS"), GET_CALL("GET_CALL"), DOWNLOAD_CALL_DOCUMENT("DOWNLOAD_CALL_DOCUMENT"), - - + /** user action context **/ - CREATE_USER("CREATE_USER"); + CREATE_USER("CREATE_USER"), + USER_LOGIN("USER_LOGIN"), + LOGOUT_USER("LOGOUT_USER"), + GET_USER("GET_USER"), + UPDATE_USER_DETAILS("UPDATE_USER_DETAILS"), + DELETE_USER("DELETE_USER"), + VALIDATE_NEW_USER_WITH_SPID_TOKEN("VALIDATE_NEW_USER_WITH_SPID_TOKEN"), + VALIDATE_EXISTING_USER_WITH_SPID_TOKEN("VALIDATE_EXISTING_USER_WITH_SPID_TOKEN"), + GET_VALID_USER_DETAILS("GET_VALID_USER_DETAILS"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java index 7e7aebe1..f68f6ae9 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/UserActionLogsEnum.java @@ -3,7 +3,14 @@ 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"), DOWNLOAD("DOWNLOAD"), UPLOAD("UPLOAD"); + LOGIN("LOGIN"), + LOGOUT("LOGOUT"), + UPDATE("UPDATE"), + DELETE("DELETE"), + VIEW("VIEW"), + INSERT("INSERT"), + DOWNLOAD("DOWNLOAD"), + UPLOAD("UPLOAD"); private final String value; diff --git a/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java b/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java index 27d2a55d..794999b0 100644 --- a/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java +++ b/src/main/java/net/gepafin/tendermanagement/enums/VersionActionTypeEnum.java @@ -3,7 +3,10 @@ package net.gepafin.tendermanagement.enums; import com.fasterxml.jackson.annotation.JsonValue; public enum VersionActionTypeEnum { - UPDATE("UPDATE"), DELETE("DELETE"), SOFT_DELETE("SOFT_DELETE"), INSERT("INSERT"); + UPDATE("UPDATE"), + DELETE("DELETE"), + SOFT_DELETE("SOFT_DELETE"), + INSERT("INSERT"); private final String 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 index bdf6249b..bde1c273 100644 --- a/src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java +++ b/src/main/java/net/gepafin/tendermanagement/model/request/UserActionRequest.java @@ -1,6 +1,7 @@ package net.gepafin.tendermanagement.model.request; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.Builder; import lombok.Data; import net.gepafin.tendermanagement.enums.UserActionContextEnum; @@ -12,4 +13,5 @@ public class UserActionRequest { private HttpServletRequest request; private UserActionLogsEnum actionType; private UserActionContextEnum actionContext; + private Object response; } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java index ba651c6f..f0163c47 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserActionsRepository.java @@ -6,4 +6,5 @@ import org.springframework.stereotype.Repository; @Repository public interface UserActionsRepository extends JpaRepository { + UserActionEntity findUserActionById(Long id); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java index 059024ab..853bb945 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/VersionHistoryRepository.java @@ -4,6 +4,13 @@ import net.gepafin.tendermanagement.entities.VersionHistoryEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface VersionHistoryRepository extends JpaRepository { + List findVersionHistoryById(Long id); + + List findVersionHistoryByUserActionIdAndUserIdNull(Long id); + + List findVersionHistoryByUserActionId(Long id); } 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 30edb908..d4dab052 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java @@ -15,16 +15,20 @@ import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.LoginAttemptResultEnum; import net.gepafin.tendermanagement.enums.LoginAttemptTypeEnum; import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.LoginReq; +import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.CompanyResponse; import net.gepafin.tendermanagement.model.response.LoginResponse; import net.gepafin.tendermanagement.model.response.RoleResponseBean; import net.gepafin.tendermanagement.model.response.UserSamlResponse; import net.gepafin.tendermanagement.model.util.JWTToken; +import net.gepafin.tendermanagement.repositories.LoginAttemptRepository; import net.gepafin.tendermanagement.repositories.SamlResponseRepository; import net.gepafin.tendermanagement.repositories.UserRepository; import net.gepafin.tendermanagement.service.HubService; import net.gepafin.tendermanagement.util.DateTimeUtil; +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; @@ -51,6 +55,7 @@ public class AuthenticationService { private final TokenProvider tokenProvider; private final AuthenticationManager authenticationManager; + @Autowired private CompanyDao companyDao; @@ -69,6 +74,15 @@ public class AuthenticationService { @Autowired private HubService hubService; + @Autowired + private HttpServletRequest request; + + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + LoginAttemptRepository loginAttemptRepository; + @Autowired public AuthenticationService(TokenProvider tokenProvider, AuthenticationManager authenticationManager) { this.tokenProvider = tokenProvider; @@ -117,9 +131,9 @@ public class AuthenticationService { return loginAttemptEntity; } - public void createSuccessLoginAttempt(LoginAttemptEntity loginAttemptEntity) { + public LoginAttemptEntity createSuccessLoginAttempt(LoginAttemptEntity loginAttemptEntity) { loginAttemptEntity.setResult(LoginAttemptResultEnum.SUCCESS.getValue()); - loginAttemptDao.createLoginAttempt(loginAttemptEntity); + return loginAttemptDao.createLoginAttempt(loginAttemptEntity); } public void createFailedLoginAttempt(LoginAttemptEntity loginAttemptEntity, String errorMsg) { loginAttemptEntity.setResult(LoginAttemptResultEnum.FAILED.getValue()); @@ -127,9 +141,11 @@ public class AuthenticationService { loginAttemptDao.createLoginAttempt(loginAttemptEntity); } public JWTToken getJWTTokenBean(UserEntity user, Boolean rememberMe, Long loginAttemptId) { + UserEntity oldUserEntity = Utils.getClonedEntityForData(user); user.setLastLogin(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); 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()); @@ -137,6 +153,9 @@ public class AuthenticationService { JWTToken jwtToken = new JWTToken(token, loginResponse); + /** This code is responsible for adding a version history log for the "Create user Or Update user" operation. **/ + loggingUtil.addVersionHistoryWithoutToken(VersionHistoryRequest.builder().request(request).oldData(oldUserEntity).newData(user).actionType(VersionActionTypeEnum.UPDATE).build()); + log.info("Login successful for email: {}", user.getEmail()); return jwtToken; } @@ -215,7 +234,8 @@ public class AuthenticationService { loginReq.setEmail(userEntity.getEmail()); loginAttemptEntity = prepareLoginAttemptEntity(loginReq, request); loginAttemptEntity.setUserId(userEntity.getId()); - return getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId()); + LoginAttemptEntity loginAttempt = createSuccessLoginAttempt(loginAttemptEntity); + return getJWTTokenBean(userEntity, Boolean.TRUE, loginAttempt.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/service/impl/UserServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java index d0b8a716..9f95b830 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java @@ -4,6 +4,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.dao.UserDao; import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; import net.gepafin.tendermanagement.model.request.LoginReq; import net.gepafin.tendermanagement.model.request.UpdateUserReq; import net.gepafin.tendermanagement.model.request.UserReq; @@ -13,6 +15,7 @@ import net.gepafin.tendermanagement.model.response.UserSamlResponse; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; import net.gepafin.tendermanagement.service.UserService; +import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Validator; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +34,9 @@ public class UserServiceImpl implements UserService { @Autowired private Validator validator; + @Autowired + private LoggingUtil loggingUtil; + @Override @Transactional(rollbackFor = Exception.class) public JWTToken createUser(HttpServletRequest request, String tempToken, UserReq userReq) { diff --git a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java index 67c8e75b..ba7a750d 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java +++ b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java @@ -9,6 +9,7 @@ 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.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.UserActionRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.repositories.UserActionsRepository; @@ -18,6 +19,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + @Slf4j @Component @RequestScope @@ -33,13 +38,15 @@ public class LoggingUtil { private UserService userService; @Autowired - TokenProvider tokenProvider; + private TokenProvider tokenProvider; public UserActionEntity logUserAction(UserActionRequest userActionRequest) { UserActionEntity userAction = new UserActionEntity(); try { String token = tokenProvider.extractTokenFromRequest(userActionRequest.getRequest()); Claims claims = tokenProvider.getClaimsFromToken(token); + Map parameters = userActionRequest.getRequest().getParameterMap(); + parameters.forEach((key, value) -> log.info("Query Parameter: {} = {}", key, Arrays.toString(value))); Long userId = claims.get(GepafinConstant.USER_ID, Long.class); Long loginAttemptId = claims.get(GepafinConstant.LOGIN_ATTEMPT_ID, Long.class); UserEntity userEntity = userService.validateUser(userId); @@ -57,7 +64,8 @@ public class LoggingUtil { userAction.setLoginAttemptId(loginAttemptId); userAction.setIpAddress(IpAddressUtils.getClientIp(userActionRequest.getRequest())); userAction.setRequestBody(requestBody); - userAction.setResponse(null); + String response = Utils.convertEntityToJsonForLogging(userActionRequest.getResponse()); + userAction.setResponse(response); userActionsRepository.save(userAction); userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId()); } catch (Exception e) { @@ -116,6 +124,31 @@ public class LoggingUtil { } } + public void logVersionHistoryWithoutToken(VersionHistoryRequest versionHistoryRequest, Long recordId, Long userActionId, String tableName) { + try { + VersionHistoryEntity history = new VersionHistoryEntity(); + Long userId = null; + String token = tokenProvider.extractTokenFromRequest(versionHistoryRequest.getRequest()); + if(versionHistoryRequest.getRequest() != null && token != null) + { + Claims claims = tokenProvider.getClaimsFromToken(token); + userId = claims.get(GepafinConstant.USER_ID, Long.class); + } + String oldData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getOldData()); + String newData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getNewData()); + history.setUserActionId(userActionId); + history.setActionType(versionHistoryRequest.getActionType().getValue()); + history.setOldData(oldData); + history.setNewData(newData); + history.setRecordId(recordId); + history.setTableName(tableName); + 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)) { @@ -132,15 +165,138 @@ public class LoggingUtil { try { Long userActionId = (Long) versionHistoryRequest.getRequest().getAttribute(GepafinConstant.USER_ACTION_ID); Long recordId = null; + String tableName = null; if(versionHistoryRequest.getNewData() != null) { - recordId = versionHistoryRequest.getNewData().getId(); + recordId = versionHistoryRequest.getNewData().getId(); + tableName = getTableName(versionHistoryRequest.getNewData().getClass()); } else if(versionHistoryRequest.getOldData() != null){ - recordId = versionHistoryRequest.getOldData().getId(); + recordId = versionHistoryRequest.getOldData().getId(); } - String tableName = getTableName(versionHistoryRequest.getNewData().getClass()); logVersionHistory(versionHistoryRequest, recordId, userActionId, tableName); } catch (Exception e) { log.error("Error adding version history: {}", e.getMessage(), e); } } + + public void addVersionHistoryWithoutToken(VersionHistoryRequest versionHistoryRequest) { + try { + Long userActionId = (Long) versionHistoryRequest.getRequest().getAttribute(GepafinConstant.USER_ACTION_ID); + Long recordId = null; + String tableName = null; + if(versionHistoryRequest.getNewData() != null) { + recordId = versionHistoryRequest.getNewData().getId(); + tableName = getTableName(versionHistoryRequest.getNewData().getClass()); + } else if(versionHistoryRequest.getOldData() != null){ + recordId = versionHistoryRequest.getOldData().getId(); + tableName = getTableName(versionHistoryRequest.getOldData().getClass()); + } + versionHistoryRequest.setNewData(versionHistoryRequest.getNewData()); + versionHistoryRequest.setRequest(versionHistoryRequest.getRequest()); + logVersionHistoryWithoutToken(versionHistoryRequest, recordId, userActionId, tableName); + } catch (Exception e) { + log.error("Error adding version history: {}", e.getMessage(), e); + } + } + + public UserActionEntity logUserActionWithoutToken(UserActionRequest userActionRequest) { + UserActionEntity userAction = new UserActionEntity(); + try { + Long userId = null; + Long loginAttemptId = null; + Long hubId = null; + String token = tokenProvider.extractTokenFromRequest(userActionRequest.getRequest()); + Map parameters = userActionRequest.getRequest().getParameterMap(); + parameters.forEach((key, value) -> log.info("Query Parameter : {} = {}", key, Arrays.toString(value))); + if(userActionRequest.getRequest() != null && token != null) + { + Claims claims = tokenProvider.getClaimsFromToken(token); + userId = claims.get(GepafinConstant.USER_ID, Long.class); + loginAttemptId = claims.get(GepafinConstant.LOGIN_ATTEMPT_ID, Long.class); + UserEntity userEntity = userService.validateUser(userId); + hubId = userEntity.getHub().getId(); + } + + String requestBody = null; + if (userActionRequest.getRequest() instanceof CachedBodyHttpServletRequest cachedRequest) { + requestBody = cachedRequest.getCachedBodyAsString(); + } + userAction.setActionType(userActionRequest.getActionType().getValue()); + userAction.setUserId(userId); + userAction.setActionContext(userActionRequest.getActionContext().getValue()); + userAction.setMethodType(userActionRequest.getRequest().getMethod()); + userAction.setHubId(hubId); + userAction.setUrl(userActionRequest.getRequest().getRequestURI()); + userAction.setLoginAttemptId(loginAttemptId); + userAction.setIpAddress(IpAddressUtils.getClientIp(userActionRequest.getRequest())); + userAction.setRequestBody(requestBody); + String response = Utils.convertEntityToJsonForLogging(userActionRequest.getResponse()); + userAction.setResponse(response); + 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; + } + public void updateUserActionWithTokenDetails(Long actionId, String token, Object response) { + + try { + UserActionEntity userAction = getUserActionLogById(actionId); + if (userAction != null && token != null) { + 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); + + userAction.setUserId(userId); + String responseData = Utils.convertEntityToJsonForLogging(response); + userAction.setResponse(responseData); + userAction.setLoginAttemptId(loginAttemptId); + userAction.setHubId(userEntity.getHub().getId()); + // Save updated entity + userActionsRepository.save(userAction); + log.info("User action log updated with token details: {}", userAction.getId()); + } + } catch (Exception e) { + log.error("Failed to update user action with token details: {}", e.getMessage(), e); + } + } + + public void updateVersionHistoriesWithTokenDetails(Long userActionId, String token) { + + try { + // Fetch version history entities for the given user action ID + List versionHistoryEntities = getVersionHistoryLogById(userActionId); + + if (versionHistoryEntities != null && !versionHistoryEntities.isEmpty() && token != null) { + // Extract claims from the token + Claims claims = tokenProvider.getClaimsFromToken(token); + Long userId = claims.get(GepafinConstant.USER_ID, Long.class); + + // Update each version history entity with the user ID + for (VersionHistoryEntity versionHistoryEntity : versionHistoryEntities) { + versionHistoryEntity.setUserId(userId); + } + + // Save all updated entities + versionHistoryRepository.saveAll(versionHistoryEntities); + + log.info("Version History logs updated with userId: {}", userId); + } else { + log.warn("No version history or token provided for userActionId: {}", userActionId); + } + } catch (Exception e) { + log.error("Failed to update version histories with token details: {}", e.getMessage(), e); + } + } + + + public UserActionEntity getUserActionLogById(Long id) { + + return userActionsRepository.findUserActionById(id); + } + public List getVersionHistoryLogById(Long id) { + + return versionHistoryRepository.findVersionHistoryByUserActionId(id); + } } diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index b414efb9..e71ac974 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -56,9 +56,8 @@ public class Utils { public static final Logger log = LoggerFactory.getLogger(Utils.class); - private static final ObjectMapper mapper = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); public static U convertObject(T source, Class destinationClass) { try { @@ -527,17 +526,14 @@ public class Utils { } try { - mapper.registerModule(new JavaTimeModule()); - mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); JsonNode entityNode = mapper.valueToTree(entity); ObjectNode objectNode = (ObjectNode) entityNode; Field[] fields = entity.getClass().getDeclaredFields(); for (Field field : fields) { - if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToMany.class) || field - .isAnnotationPresent(OneToOne.class) || field.isAnnotationPresent(ManyToMany.class)) { + if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToMany.class) || field.isAnnotationPresent( + OneToOne.class) || field.isAnnotationPresent(ManyToMany.class)) { objectNode.remove(field.getName()); } } 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 e4a8669d..13a01c5a 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 @@ -76,7 +76,7 @@ public class CallApiController implements CallApi { .body(new Response<>(createCallResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.CALL_UPDATE_SUCCESSFULLY_MSG))); } @Override - @Transactional(readOnly = true) + @Transactional public ResponseEntity> getCallById(HttpServletRequest request, Long callId,Long companyId) { /** This code is responsible for creating user action logs for the "get Call by id" operation. **/ diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java index 1a3040c1..0b170375 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java @@ -4,7 +4,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.GepafinConstant; +import net.gepafin.tendermanagement.entities.UserActionEntity; +import net.gepafin.tendermanagement.enums.UserActionContextEnum; +import net.gepafin.tendermanagement.enums.UserActionLogsEnum; import net.gepafin.tendermanagement.enums.UserStatusEnum; import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.UserSamlResponse; @@ -12,6 +16,7 @@ import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; import net.gepafin.tendermanagement.model.util.Response; import net.gepafin.tendermanagement.service.UserService; +import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.web.rest.api.UserApi; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.slf4j.Logger; @@ -35,12 +40,27 @@ public class UserApiController implements UserApi { @Autowired private UserService userService; + @Autowired + private LoggingUtil loggingUtil; + + @Autowired + private TokenProvider tokenProvider; + @Override public ResponseEntity> createUser(HttpServletRequest request, String tempToken, @RequestBody UserReq userReq) { log.info("Create User with - Request Body: {}", userReq); + + /** This code is responsible for creating user action logs for the "Create User" operation, even tempToken and userToken is present or not**/ + UserActionEntity userAction = loggingUtil.logUserActionWithoutToken(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.INSERT).actionContext(UserActionContextEnum.CREATE_USER).build()); + JWTToken createdUser = userService.createUser(request, tempToken, userReq); - return ResponseEntity.status(HttpStatus.CREATED) - .body(new Response<>(createdUser, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_CREATED_SUCCESS_MSG))); + + /** This code is responsible for updating the user action log with new data after token generation.**/ + if (userAction != null) { + loggingUtil.updateUserActionWithTokenDetails(userAction.getId(), createdUser.getToken(), null ); + } + + return ResponseEntity.status(HttpStatus.CREATED).body(new Response<>(createdUser, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_CREATED_SUCCESS_MSG))); } @Override @@ -48,34 +68,58 @@ public class UserApiController implements UserApi { @PathVariable("userId") Long userId, @Valid @RequestBody UpdateUserReq userReq) { log.info("Update User - User ID: {}, Request Body: {}", userId, userReq); + + /** This code is responsible for "Updating user details" operation. **/ + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.UPDATE) + .actionContext(UserActionContextEnum.UPDATE_USER_DETAILS).build()); + UserResponseBean updatedUser = userService.updateUser(request, userId, userReq); + return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(updatedUser, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_UPDATED_SUCCESS_MSG))); } @Override - public ResponseEntity> getUserById(HttpServletRequest request, - @PathVariable("userId") Long userId) { + public ResponseEntity> getUserById(HttpServletRequest request, @PathVariable("userId") Long userId) { + log.info("Get User by ID - User ID: {}", userId); + + /** This code is responsible for creating user action logs for the "Get user" operation. **/ + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW).actionContext(UserActionContextEnum.GET_USER).build()); + UserResponseBean user = userService.getUserById(request, userId); - return ResponseEntity.status(HttpStatus.OK) - .body(new Response<>(user, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_USER_SUCCESS_MSG))); + + return ResponseEntity.status(HttpStatus.OK).body(new Response<>(user, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_USER_SUCCESS_MSG))); } @Override - public ResponseEntity> deleteUser(HttpServletRequest request, - @PathVariable("userId") Long userId) { + public ResponseEntity> deleteUser(HttpServletRequest request, @PathVariable("userId") Long userId) { log.info("Delete User - User ID: {}", userId); + + /** This code is responsible for creating user action logs for the "Delete user" operation. **/ + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.DELETE).actionContext(UserActionContextEnum.DELETE_USER).build()); + userService.deleteUser(request, userId); - return ResponseEntity.status(HttpStatus.OK) - .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_DELETED_SUCCESS_MSG))); + + return ResponseEntity.status(HttpStatus.OK).body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.USER_DELETED_SUCCESS_MSG))); } @Override - public ResponseEntity> login(HttpServletRequest request, - @Valid @RequestBody LoginReq loginReq) { + public ResponseEntity> login(HttpServletRequest request, @Valid @RequestBody LoginReq loginReq) { + log.info("User login attempt "); - JWTToken jwtToken = userService.login(loginReq,request); + + /** This code is responsible for creating user action logs for the "LOGIN" operation. **/ + UserActionEntity userAction = loggingUtil.logUserActionWithoutToken( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.INSERT).actionContext(UserActionContextEnum.USER_LOGIN).build()); + + JWTToken jwtToken = userService.login(loginReq, request); + + /** This code is responsible for updating the action and version log with new data after token generation.**/ + if (userAction != null) { + loggingUtil.updateUserActionWithTokenDetails(userAction.getId(), jwtToken.getToken(), null); + loggingUtil.updateVersionHistoriesWithTokenDetails(userAction.getId(), jwtToken.getToken()); + } return ResponseEntity.ok(new Response<>(jwtToken, Status.SUCCESS, Translator.toLocale(GepafinConstant.LOGIN_SUCCESS_MSG))); } @Override @@ -106,6 +150,10 @@ public class UserApiController implements UserApi { @Override public ResponseEntity> logoutUser(HttpServletRequest request, HttpServletResponse response) { + + /** This code is responsible for creating user action logs for the "Logout" operation. **/ + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.LOGOUT).actionContext(UserActionContextEnum.LOGOUT_USER).build()); + userService.logoutUser(request, response); log.info("User has successfully logged"); @@ -123,24 +171,50 @@ public class UserApiController implements UserApi { public ResponseEntity> getValidUser(HttpServletRequest request) { log.info("Get Valid User Detail"); UserResponseBean user = userService.getValidUser(request); + + /** This code is responsible for creating user action logs for the "Get valid user details." operation. **/ + loggingUtil.logUserAction(UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW).actionContext(UserActionContextEnum.GET_VALID_USER_DETAILS).build()); + return ResponseEntity.status(HttpStatus.OK) .body(new Response<>(user, Status.SUCCESS, Translator.toLocale(GepafinConstant.GET_USER_SUCCESS_MSG))); } - + @Override public ResponseEntity> validateExistingUserToken(HttpServletRequest request, String token) { - log.info("User login attempt via spid token"); - JWTToken data = userService.validateExistingUserToken(request, token); - return ResponseEntity.ok(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.TOKEN_VALIDATE_SUCCESS_MSE))); + + log.info("User login attempt via spid token"); + + /** This code is responsible for creating user action logs for the "Validating existing user from spid token" operation. **/ + UserActionEntity userAction = loggingUtil.logUserActionWithoutToken( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW).actionContext(UserActionContextEnum.VALIDATE_EXISTING_USER_WITH_SPID_TOKEN) + .build()); + + JWTToken data = userService.validateExistingUserToken(request, token); + + /** This code is responsible for updating the action and version log with new data.**/ + if (userAction != null) { + loggingUtil.updateUserActionWithTokenDetails(userAction.getId(), data.getToken(), data); + loggingUtil.updateVersionHistoriesWithTokenDetails(userAction.getId(), data.getToken()); + } + + return ResponseEntity.ok(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.TOKEN_VALIDATE_SUCCESS_MSG))); } - + @Override public ResponseEntity> validateNewUserToken(HttpServletRequest request, String token) { - log.info("User validating spid token"); - UserSamlResponse data = userService.validateNewUserToken(request,token); - return ResponseEntity.ok(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.TOKEN_VALIDATE_SUCCESS_MSE))); + + log.info("User validating spid token"); + + /** This code is responsible for creating user action logs for the "Validating new user from spid token" operation. **/ + loggingUtil.logUserActionWithoutToken( + UserActionRequest.builder().request(request).actionType(UserActionLogsEnum.VIEW).actionContext(UserActionContextEnum.VALIDATE_NEW_USER_WITH_SPID_TOKEN).build()); + + UserSamlResponse data = userService.validateNewUserToken(request, token); + + return ResponseEntity.ok(new Response<>(data, Status.SUCCESS, Translator.toLocale(GepafinConstant.TOKEN_VALIDATE_SUCCESS_MSG))); } + @Override public ResponseEntity>> getAllUsers(HttpServletRequest request, Long roleId) { 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 b4c88aa2..67437ae5 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 @@ -1779,4 +1779,11 @@ constraintName="fk_version_history_user_action"/> + + + + + + +