From 3c5b5b895cd4bc6d1bdddfce596ce9f6054ea0e2 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 6 May 2026 12:18:05 +0530 Subject: [PATCH 01/18] add bengali language translation on dynamic forms --- .../iemr/common/data/dynamic_from/FormFieldOption.java | 3 +++ .../com/iemr/common/data/translation/Translation.java | 2 ++ .../service/dynamicForm/FormMasterServiceImpl.java | 9 ++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java b/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java index 8cfeb0de..05f06df2 100644 --- a/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java +++ b/src/main/java/com/iemr/common/data/dynamic_from/FormFieldOption.java @@ -27,6 +27,9 @@ public class FormFieldOption { @Column(name = "label_as") private String labelAs; + @Column(name = "label_bn") + private String labelBn; + @Column(name = "sort_order") private Integer sortOrder; diff --git a/src/main/java/com/iemr/common/data/translation/Translation.java b/src/main/java/com/iemr/common/data/translation/Translation.java index 0dad116d..e0104a53 100644 --- a/src/main/java/com/iemr/common/data/translation/Translation.java +++ b/src/main/java/com/iemr/common/data/translation/Translation.java @@ -20,6 +20,8 @@ public class Translation { private String hindiTranslation; @Column(name = "assamese_translation") private String assameseTranslation; + @Column(name = "bengali_translation") + private String bengaliTranslation; @Column(name = "is_active") private Boolean isActive; } diff --git a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java index 0bf1c2fe..38154f4a 100644 --- a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java +++ b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java @@ -160,6 +160,9 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str } else if ("en".equalsIgnoreCase(lang)) { translatedLabel = label.getEnglish(); + }else if ("bn".equalsIgnoreCase(lang)) { + translatedLabel = label.getBengaliTranslation(); + } } @@ -171,6 +174,9 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str } else if ("en".equalsIgnoreCase(lang)) { translatedPlaceHolder = placeHolder.getEnglish(); + } else if ("bn".equalsIgnoreCase(lang)) { + translatedPlaceHolder = placeHolder.getBengaliTranslation(); + } } @@ -203,7 +209,8 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str map.put("value", opt.getValue()); if ("hi".equalsIgnoreCase(lang)) map.put("label", opt.getLabelHi()); else if ("as".equalsIgnoreCase(lang)) map.put("label", opt.getLabelAs()); - else map.put("label", opt.getLabelEn()); + else if("en".equals(lang)) map.put("label", opt.getLabelEn()); + else if("bn".equals(lang)) map.put("label", opt.getLabelBn()); return map; }) .collect(Collectors.toList()); From bc54def0d8c37942baf5b12d65f4ee09a1a3afc0 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Tue, 12 May 2026 13:17:28 +0530 Subject: [PATCH 02/18] change api version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4e8c0d9..ffe74404 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iemr.common-API common-api - 3.8.1 + 3.8.2 war Common-API From 3e0bbb798eb8b1894858ab7caa438976866fea9b Mon Sep 17 00:00:00 2001 From: SnehaRH <77656297+snehar-nd@users.noreply.github.com> Date: Fri, 22 May 2026 19:05:21 +0530 Subject: [PATCH 03/18] Sn/3.8.1 (#423) * fix: aam-2313 phone number leading with zero - removed zero * fix: allow concurrent sessions for admin, superadmin, and supervisor roles Co-Authored-By: Claude Sonnet 4.6 * fix: added admin and superadmin for the condition --------- Co-authored-by: Claude Sonnet 4.6 --- .../controller/users/IEMRAdminController.java | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 349c3b1e..5ac134cd 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -81,6 +81,7 @@ public class IEMRAdminController { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private InputMapper inputMapper = new InputMapper(); + private static final Set CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("admin", "superadmin"); // @Value("${captcha.enable-captcha}") private boolean enableCaptcha =false; @@ -180,11 +181,22 @@ public String userAuthenticate( if (m_User.getUserName() != null && (m_User.getDoLogout() == null || !m_User.getDoLogout()) && (m_User.getWithCredentials() != null && m_User.getWithCredentials())) { - String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( - m_User.getUserName().trim().toLowerCase()); - if (tokenFromRedis != null) { - throw new IEMRException( - "You are already logged in,please confirm to logout from other device and login again"); + String userRole = ""; + if (mUser.size() == 1 && mUser.get(0).getM_UserServiceRoleMapping() != null) { + for (UserServiceRoleMapping usrm : mUser.get(0).getM_UserServiceRoleMapping()) { + if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) { + userRole = usrm.getM_Role().getRoleName(); + break; + } + } + } + if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) { + String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( + m_User.getUserName().trim().toLowerCase()); + if (tokenFromRedis != null) { + throw new IEMRException( + "You are already logged in,please confirm to logout from other device and login again"); + } } } else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) { deleteSessionObject(m_User.getUserName().trim().toLowerCase()); @@ -412,16 +424,28 @@ public String logOutUserFromConcurrentSession( deleteSessionObjectByGettingSessionDetails(previousTokenFromRedis); sessionObject.deleteSessionObject(previousTokenFromRedis); - // Denylist the active JWT so System 1's requests are immediately rejected - String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); - String jtiData = stringRedisTemplate.opsForValue().get("jti:" + usernameKey); - if (jtiData != null) { - String[] parts = jtiData.split("\\|", 2); - tokenDenylist.addTokenToDenylist(parts[0], jwtUtil.getAccessTokenExpiration()); - if (parts.length > 1) { - redisTemplate.delete("user_" + parts[1]); + String userRole = ""; + if (mUsers.get(0).getM_UserServiceRoleMapping() != null) { + for (UserServiceRoleMapping usrm : mUsers.get(0).getM_UserServiceRoleMapping()) { + if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) { + userRole = usrm.getM_Role().getRoleName(); + break; + } + } + } + if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) { + // Denylist the active JWT so the first system's requests are immediately rejected + String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); + String jtiData = (String) redisTemplate.opsForValue().get("jti:" + usernameKey); + if (jtiData != null) { + String[] parts = jtiData.split("\\|", 2); + String jti = parts[0]; + tokenDenylist.addTokenToDenylist(jti, jwtUtil.getAccessTokenExpiration()); + if (parts.length > 1) { + redisTemplate.delete("user_" + parts[1]); + } + redisTemplate.delete("jti:" + usernameKey); } - stringRedisTemplate.delete("jti:" + usernameKey); } response.setResponse("User successfully logged out"); From cbb18177013dedbed09d83cddcb0414d2aab162d Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 22 May 2026 20:13:52 +0530 Subject: [PATCH 04/18] fix: use stringRedisTemplate for jti: key read/delete in concurrent session logout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit redisTemplate has Jackson2JsonRedisSerializer as value serializer, so reading the plain-string jti: value caused a deserialization failure (statusCode 5000). jti: keys are written via stringRedisTemplate at login, so reads and deletes must also use stringRedisTemplate — restoring the behaviour from commit 80fa0e5e that was accidentally reverted in #423. Co-Authored-By: Claude Sonnet 4.6 --- .../com/iemr/common/controller/users/IEMRAdminController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 5ac134cd..f9942b5e 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -436,7 +436,7 @@ public String logOutUserFromConcurrentSession( if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) { // Denylist the active JWT so the first system's requests are immediately rejected String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); - String jtiData = (String) redisTemplate.opsForValue().get("jti:" + usernameKey); + String jtiData = stringRedisTemplate.opsForValue().get("jti:" + usernameKey); if (jtiData != null) { String[] parts = jtiData.split("\\|", 2); String jti = parts[0]; @@ -444,7 +444,7 @@ public String logOutUserFromConcurrentSession( if (parts.length > 1) { redisTemplate.delete("user_" + parts[1]); } - redisTemplate.delete("jti:" + usernameKey); + stringRedisTemplate.delete("jti:" + usernameKey); } } From 8b548d383988d62fa2354b08f95124fdda643387 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 22 May 2026 20:48:34 +0530 Subject: [PATCH 05/18] fix: correct exempt roles and allow superadmin concurrent sessions - Fix role name from 'admin' to 'provideradmin' to match actual DB value - Add concurrent session exemption to superUserAuthenticate so SuperAdmin can log in from multiple tabs without being blocked Co-Authored-By: Claude Sonnet 4.6 --- .../controller/users/IEMRAdminController.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index f9942b5e..f23c1739 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -81,7 +81,7 @@ public class IEMRAdminController { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private InputMapper inputMapper = new InputMapper(); - private static final Set CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("admin", "superadmin"); + private static final Set CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("provideradmin", "superadmin"); // @Value("${captcha.enable-captcha}") private boolean enableCaptcha =false; @@ -560,11 +560,13 @@ public String superUserAuthenticate( String refreshToken = null; boolean isMobile = false; if (m_User.getUserName() != null && (m_User.getDoLogout() == null || m_User.getDoLogout() == false)) { - String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( - m_User.getUserName().trim().toLowerCase()); - if (tokenFromRedis != null) { - throw new IEMRException( - "You are already logged in,please confirm to logout from other device and login again"); + if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(m_User.getUserName().trim().toLowerCase())) { + String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( + m_User.getUserName().trim().toLowerCase()); + if (tokenFromRedis != null) { + throw new IEMRException( + "You are already logged in,please confirm to logout from other device and login again"); + } } } else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) { deleteSessionObject(m_User.getUserName().trim().toLowerCase()); From 2c23d9374afc6f3c4d88873a0627fd6b32e80be3 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 29 May 2026 12:25:25 +0530 Subject: [PATCH 06/18] Implement feature multiple login attempt --- .../controller/users/IEMRAdminController.java | 89 ++++++++++++++++++- .../service/users/IEMRAdminUserService.java | 2 +- .../users/IEMRAdminUserServiceImpl.java | 5 ++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 845fe890..ec0298e8 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -171,7 +171,93 @@ public String userAuthenticate( } String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword()); - List mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword); + // Fetch user + List existingUser = iemrAdminUserServiceImpl.userExitsCheck(m_User.getUserName()); + + /* + * ========================================= + * ACCOUNT LOCK CHECK + * ========================================= + */ + if(!existingUser.isEmpty()){ + if (existingUser.get(0) != null + && existingUser.get(0).getFailedAttempt() != null + && existingUser.get(0).getFailedAttempt() >= 5) { + + throw new IEMRException( + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); + } + } + + + List mUser = iemrAdminUserServiceImpl + .userAuthenticate(m_User.getUserName(), decryptPassword); + + /* + * ========================================= + * FAILED LOGIN ATTEMPT LOGIC + * ========================================= + */ + if (mUser == null || mUser.isEmpty()) { + if(!existingUser.isEmpty()){ + if (existingUser != null) { + + Integer failedAttempt = existingUser.get(0).getFailedAttempt() != null + ? existingUser.get(0).getFailedAttempt() + : 0; + + failedAttempt++; + + existingUser.get(0).setFailedAttempt(failedAttempt); + + iemrAdminUserServiceImpl.save(existingUser.get(0)); + + int remainingAttempts = 5 - failedAttempt; + + // Lock account on 5th attempt + if (failedAttempt >= 5) { + + + + response.setError(new IEMRException( + "Your account has been locked due to multiple failed login attempts.")); + return response.toString(); + } + + // Warning on 3rd attempt + if (failedAttempt == 4) { + + + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts + + ". If you enter wrong username or password again, your account will be locked.")); + return response.toString(); + } + + + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts)); + return response.toString(); + + } + } + + + throw new IEMRException("Invalid username or password."); + } + + /* + * ========================================= + * RESET FAILED ATTEMPTS ON SUCCESS LOGIN + * ========================================= + */ + User loggedInUser = mUser.get(0); + + loggedInUser.setFailedAttempt(0); + + iemrAdminUserServiceImpl.save(loggedInUser); JSONObject resMap = new JSONObject(); JSONObject serviceRoleMultiMap = new JSONObject(); JSONObject serviceRoleMap = new JSONObject(); @@ -253,7 +339,6 @@ public String userAuthenticate( // Facility data for ALL users - common pattern, empty if not applicable try { if (mUser.size() == 1) { - User loggedInUser = mUser.get(0); String userRoleName = ""; if (loggedInUser.getM_UserServiceRoleMapping() != null) { for (UserServiceRoleMapping usrm : loggedInUser.getM_UserServiceRoleMapping()) { diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java index 26b7bb15..1db05322 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java @@ -126,5 +126,5 @@ public List getUserServiceRoleMappingForProvider(Integ List findUserIdByUserName(String userName) throws IEMRException; - + User save(User loggedInUser); } diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 71d72c97..3d20be1b 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -1230,4 +1230,9 @@ public List findUserIdByUserName(String userName) { return iEMRUserRepositoryCustom.findUserName(userName); } + + @Override + public User save(User loggedInUser) { + return iEMRUserRepositoryCustom.save(loggedInUser); + } } From fe3336a170ac1bf3d2271d26665fc5888b5dbfdc Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 29 May 2026 13:09:15 +0530 Subject: [PATCH 07/18] Implement feature multiple login attempt --- .../controller/users/IEMRAdminController.java | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index ec0298e8..b3644743 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -198,54 +198,49 @@ public String userAuthenticate( * FAILED LOGIN ATTEMPT LOGIC * ========================================= */ - if (mUser == null || mUser.isEmpty()) { - if(!existingUser.isEmpty()){ - if (existingUser != null) { - - Integer failedAttempt = existingUser.get(0).getFailedAttempt() != null - ? existingUser.get(0).getFailedAttempt() - : 0; - - failedAttempt++; + if(!existingUser.isEmpty()){ + if (existingUser != null) { - existingUser.get(0).setFailedAttempt(failedAttempt); + Integer failedAttempt = existingUser.get(0).getFailedAttempt() != null + ? existingUser.get(0).getFailedAttempt() + : 0; - iemrAdminUserServiceImpl.save(existingUser.get(0)); + failedAttempt++; - int remainingAttempts = 5 - failedAttempt; + existingUser.get(0).setFailedAttempt(failedAttempt); - // Lock account on 5th attempt - if (failedAttempt >= 5) { + iemrAdminUserServiceImpl.save(existingUser.get(0)); + int remainingAttempts = 5 - failedAttempt; + // Lock account on 5th attempt + if (failedAttempt >= 5) { - response.setError(new IEMRException( - "Your account has been locked due to multiple failed login attempts.")); - return response.toString(); - } - // Warning on 3rd attempt - if (failedAttempt == 4) { + response.setError(new IEMRException( + "Your account has been locked due to multiple failed login attempts.")); + return response.toString(); + } - response.setError(new IEMRException( - "Invalid username or password. Remaining attempts: " - + remainingAttempts - + ". If you enter wrong username or password again, your account will be locked.")); - return response.toString(); - } + // Warning on 3rd attempt + if (failedAttempt == 4) { - response.setError(new IEMRException( - "Invalid username or password. Remaining attempts: " - + remainingAttempts)); - return response.toString(); + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts + + ". If you enter wrong username or password again, your account will be locked.")); + return response.toString(); + } - } - } + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts)); + return response.toString(); - throw new IEMRException("Invalid username or password."); + } } /* From a6a6d3c4814db3d45c3d3f3e88bdbb5fd6c1adeb Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 12 Jun 2026 13:56:13 +0530 Subject: [PATCH 08/18] fix: delete camp:vanID and camp:parkingPlaceID from Redis on MMU logout Redis keys were stored on MMU login with 30-day TTL but never deleted on logout. Added cleanup in userLogout() so stale camp config does not persist after the MMU session ends. Co-Authored-By: Claude Sonnet 4.6 --- .../iemr/common/controller/users/IEMRAdminController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index f23c1739..a82374a5 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -1018,6 +1018,13 @@ public String userLogout(HttpServletRequest request) { try { deleteSessionObjectByGettingSessionDetails(request.getHeader("Authorization")); sessionObject.deleteSessionObject(request.getHeader("Authorization")); + try { + stringRedisTemplate.delete("camp:vanID"); + stringRedisTemplate.delete("camp:parkingPlaceID"); + logger.info("Camp config cleared from Redis on MMU logout"); + } catch (Exception redisEx) { + logger.warn("Failed to clear camp Redis keys on logout: {}", redisEx.getMessage()); + } response.setResponse("Success"); } catch (Exception e) { response.setError(e); From 758bf3674694bad1177f15cb5a7f95fb5986c2f1 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 16 Jun 2026 15:11:41 +0530 Subject: [PATCH 09/18] feat: add endpoint to expose server's LAN IP address Lets a device on the same network discover the API's LAN IP and port without needing it typed in manually, and bumps version to 3.8.2. --- pom.xml | 2 +- .../controller/connect/ConnectController.java | 57 +++++++++++++++++++ .../com/iemr/common/utils/NetworkUtil.java | 56 ++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/iemr/common/controller/connect/ConnectController.java create mode 100644 src/main/java/com/iemr/common/utils/NetworkUtil.java diff --git a/pom.xml b/pom.xml index b4e8c0d9..ffe74404 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iemr.common-API common-api - 3.8.1 + 3.8.2 war Common-API diff --git a/src/main/java/com/iemr/common/controller/connect/ConnectController.java b/src/main/java/com/iemr/common/controller/connect/ConnectController.java new file mode 100644 index 00000000..7ecf45ba --- /dev/null +++ b/src/main/java/com/iemr/common/controller/connect/ConnectController.java @@ -0,0 +1,57 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.controller.connect; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.iemr.common.utils.NetworkUtil; + +import io.swagger.v3.oas.annotations.Operation; + +/** + * Exposes the server's LAN address so a mobile device on the same wifi + * network can connect to this API. + */ +@RestController +@RequestMapping(value = "/public/connect") +public class ConnectController { + + @Value("${server.port:8080}") + private int serverPort; + + @Operation(summary = "Get the server's LAN IP address and port") + @GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getConnectInfo() { + Map response = new LinkedHashMap<>(); + response.put("ip", NetworkUtil.getLanIPAddress()); + response.put("port", serverPort); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/iemr/common/utils/NetworkUtil.java b/src/main/java/com/iemr/common/utils/NetworkUtil.java new file mode 100644 index 00000000..b5ded4d5 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/NetworkUtil.java @@ -0,0 +1,56 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.utils; + +import java.net.DatagramSocket; +import java.net.InetAddress; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Resolves the single LAN-facing IPv4 address the OS would use for outbound + * traffic, used to build a URL that a device on the same wifi network (e.g. + * a mobile phone) can connect to. A machine can have several network + * interfaces (wifi, ethernet, VPN, virtual adapters); connecting a UDP + * socket to an external address - without sending any packet - makes the OS + * routing table pick the one real outbound interface, avoiding ambiguity + * from iterating all interfaces. + */ +public final class NetworkUtil { + + private static final Logger logger = LoggerFactory.getLogger(NetworkUtil.class); + private static final String FALLBACK_IP = "127.0.0.1"; + + private NetworkUtil() { + } + + public static String getLanIPAddress() { + try (DatagramSocket socket = new DatagramSocket()) { + socket.connect(InetAddress.getByName("8.8.8.8"), 10002); + return socket.getLocalAddress().getHostAddress(); + } catch (Exception e) { + logger.error("Failed to resolve LAN IP address", e); + return FALLBACK_IP; + } + } +} From be92d7c5cbbd993b569213194eb691e803ef91d0 Mon Sep 17 00:00:00 2001 From: snehar-nd Date: Tue, 16 Jun 2026 17:14:44 +0530 Subject: [PATCH 10/18] fix: extented the token expiry timing --- src/main/java/com/iemr/common/utils/CookieUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/utils/CookieUtil.java b/src/main/java/com/iemr/common/utils/CookieUtil.java index 92c071c5..1562e3c4 100644 --- a/src/main/java/com/iemr/common/utils/CookieUtil.java +++ b/src/main/java/com/iemr/common/utils/CookieUtil.java @@ -35,8 +35,8 @@ public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response, H // Make the cookie HttpOnly to prevent JavaScript access for security cookie.setHttpOnly(true); - // Set the Max-Age (expiry time) in seconds (8 hours) - cookie.setMaxAge(60 * 60 * 8); // 8 hours expiration + // Set the Max-Age (expiry time) in seconds (10 hours) + cookie.setMaxAge(60 * 60 * 10); // 10 hours expiration // Set the path to "/" so the cookie is available across the entire application cookie.setPath("/"); From e04714b8c97315440bde52955a8a7ecfd8211ec8 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 22 Jun 2026 15:26:27 +0530 Subject: [PATCH 11/18] fix issue of feature multiple login attempt --- .../users/IEMRAdminUserServiceImpl.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 3d20be1b..13adda14 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -265,12 +265,28 @@ public List userAuthenticate(String userName, String password) throws Exce checkUserAccountStatus(user); iEMRUserRepositoryCustom.save(user); } else if (validatePassword == 0) { - if (user.getFailedAttempt() + 1 < failedAttempt) { - user.setFailedAttempt(user.getFailedAttempt() + 1); + int currentFailedAttempt = + user.getFailedAttempt() != null ? user.getFailedAttempt() : 0; + + int newFailedAttempt = currentFailedAttempt + 1; + int remainingAttempts = failedAttempt - newFailedAttempt; + if (newFailedAttempt < failedAttempt) { + + user.setFailedAttempt(newFailedAttempt); user = iEMRUserRepositoryCustom.save(user); + logger.warn("User Password Wrong"); - throw new IEMRException("Invalid username or password"); - } else if (user.getFailedAttempt() + 1 >= failedAttempt) { + + if (remainingAttempts == 1) { + throw new IEMRException( + "Invalid username or password. Remaining attempts: 1. " + + "If you enter wrong username or password again, your account will be locked."); + } + + throw new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts); + }else if (user.getFailedAttempt() + 1 >= failedAttempt) { user.setFailedAttempt(user.getFailedAttempt() + 1); user.setDeleted(true); user = iEMRUserRepositoryCustom.save(user); @@ -278,14 +294,14 @@ public List userAuthenticate(String userName, String password) throws Exce ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Invalid username or password. Please contact administrator."); + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); } else { user.setFailedAttempt(user.getFailedAttempt() + 1); user = iEMRUserRepositoryCustom.save(user); logger.warn("Failed login attempt {} of {} for a user account.", user.getFailedAttempt(), ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Invalid username or password. Please contact administrator."); + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); } } else { checkUserAccountStatus(user); From 818257c3535b4dff2c26aa0a4af200bec635b0fd Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 22 Jun 2026 16:44:01 +0530 Subject: [PATCH 12/18] fix issue of feature multiple login attempt --- .../users/IEMRAdminUserServiceImpl.java | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 13adda14..3d20be1b 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -265,28 +265,12 @@ public List userAuthenticate(String userName, String password) throws Exce checkUserAccountStatus(user); iEMRUserRepositoryCustom.save(user); } else if (validatePassword == 0) { - int currentFailedAttempt = - user.getFailedAttempt() != null ? user.getFailedAttempt() : 0; - - int newFailedAttempt = currentFailedAttempt + 1; - int remainingAttempts = failedAttempt - newFailedAttempt; - if (newFailedAttempt < failedAttempt) { - - user.setFailedAttempt(newFailedAttempt); + if (user.getFailedAttempt() + 1 < failedAttempt) { + user.setFailedAttempt(user.getFailedAttempt() + 1); user = iEMRUserRepositoryCustom.save(user); - logger.warn("User Password Wrong"); - - if (remainingAttempts == 1) { - throw new IEMRException( - "Invalid username or password. Remaining attempts: 1. " - + "If you enter wrong username or password again, your account will be locked."); - } - - throw new IEMRException( - "Invalid username or password. Remaining attempts: " - + remainingAttempts); - }else if (user.getFailedAttempt() + 1 >= failedAttempt) { + throw new IEMRException("Invalid username or password"); + } else if (user.getFailedAttempt() + 1 >= failedAttempt) { user.setFailedAttempt(user.getFailedAttempt() + 1); user.setDeleted(true); user = iEMRUserRepositoryCustom.save(user); @@ -294,14 +278,14 @@ public List userAuthenticate(String userName, String password) throws Exce ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Your account has been locked due to multiple failed login attempts. Please contact administrator."); + "Invalid username or password. Please contact administrator."); } else { user.setFailedAttempt(user.getFailedAttempt() + 1); user = iEMRUserRepositoryCustom.save(user); logger.warn("Failed login attempt {} of {} for a user account.", user.getFailedAttempt(), ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Your account has been locked due to multiple failed login attempts. Please contact administrator."); + "Invalid username or password. Please contact administrator."); } } else { checkUserAccountStatus(user); From a27db85fa8bf9c5324245b756fff31cd5ea4ec61 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Mon, 22 Jun 2026 17:07:04 +0530 Subject: [PATCH 13/18] fix issue of feature multiple login attempt --- .../controller/users/IEMRAdminController.java | 71 ------------------- .../users/IEMRAdminUserServiceImpl.java | 28 ++++++-- 2 files changed, 22 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index b3644743..f3612fdb 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -171,83 +171,12 @@ public String userAuthenticate( } String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword()); - // Fetch user - List existingUser = iemrAdminUserServiceImpl.userExitsCheck(m_User.getUserName()); - - /* - * ========================================= - * ACCOUNT LOCK CHECK - * ========================================= - */ - if(!existingUser.isEmpty()){ - if (existingUser.get(0) != null - && existingUser.get(0).getFailedAttempt() != null - && existingUser.get(0).getFailedAttempt() >= 5) { - - throw new IEMRException( - "Your account has been locked due to multiple failed login attempts. Please contact administrator."); - } - } List mUser = iemrAdminUserServiceImpl .userAuthenticate(m_User.getUserName(), decryptPassword); - /* - * ========================================= - * FAILED LOGIN ATTEMPT LOGIC - * ========================================= - */ - if(!existingUser.isEmpty()){ - if (existingUser != null) { - - Integer failedAttempt = existingUser.get(0).getFailedAttempt() != null - ? existingUser.get(0).getFailedAttempt() - : 0; - - failedAttempt++; - - existingUser.get(0).setFailedAttempt(failedAttempt); - - iemrAdminUserServiceImpl.save(existingUser.get(0)); - - int remainingAttempts = 5 - failedAttempt; - - // Lock account on 5th attempt - if (failedAttempt >= 5) { - - - - response.setError(new IEMRException( - "Your account has been locked due to multiple failed login attempts.")); - return response.toString(); - } - - // Warning on 3rd attempt - if (failedAttempt == 4) { - - - response.setError(new IEMRException( - "Invalid username or password. Remaining attempts: " - + remainingAttempts - + ". If you enter wrong username or password again, your account will be locked.")); - return response.toString(); - } - - - response.setError(new IEMRException( - "Invalid username or password. Remaining attempts: " - + remainingAttempts)); - return response.toString(); - - } - } - /* - * ========================================= - * RESET FAILED ATTEMPTS ON SUCCESS LOGIN - * ========================================= - */ User loggedInUser = mUser.get(0); loggedInUser.setFailedAttempt(0); diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 3d20be1b..13adda14 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -265,12 +265,28 @@ public List userAuthenticate(String userName, String password) throws Exce checkUserAccountStatus(user); iEMRUserRepositoryCustom.save(user); } else if (validatePassword == 0) { - if (user.getFailedAttempt() + 1 < failedAttempt) { - user.setFailedAttempt(user.getFailedAttempt() + 1); + int currentFailedAttempt = + user.getFailedAttempt() != null ? user.getFailedAttempt() : 0; + + int newFailedAttempt = currentFailedAttempt + 1; + int remainingAttempts = failedAttempt - newFailedAttempt; + if (newFailedAttempt < failedAttempt) { + + user.setFailedAttempt(newFailedAttempt); user = iEMRUserRepositoryCustom.save(user); + logger.warn("User Password Wrong"); - throw new IEMRException("Invalid username or password"); - } else if (user.getFailedAttempt() + 1 >= failedAttempt) { + + if (remainingAttempts == 1) { + throw new IEMRException( + "Invalid username or password. Remaining attempts: 1. " + + "If you enter wrong username or password again, your account will be locked."); + } + + throw new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts); + }else if (user.getFailedAttempt() + 1 >= failedAttempt) { user.setFailedAttempt(user.getFailedAttempt() + 1); user.setDeleted(true); user = iEMRUserRepositoryCustom.save(user); @@ -278,14 +294,14 @@ public List userAuthenticate(String userName, String password) throws Exce ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Invalid username or password. Please contact administrator."); + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); } else { user.setFailedAttempt(user.getFailedAttempt() + 1); user = iEMRUserRepositoryCustom.save(user); logger.warn("Failed login attempt {} of {} for a user account.", user.getFailedAttempt(), ConfigProperties.getInteger("failedLoginAttempt")); throw new IEMRException( - "Invalid username or password. Please contact administrator."); + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); } } else { checkUserAccountStatus(user); From c3013e0a0b46c08369ee7ca6595673df9a1b79b4 Mon Sep 17 00:00:00 2001 From: Saurav Mishra <80103738+SauravBizbRolly@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:11:36 +0530 Subject: [PATCH 14/18] Increase timing of refresh token (#435) --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4a2de342..bc7a8c14 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -177,7 +177,7 @@ account.lock.duration.hours=24 #Jwt Token configuration jwt.access.expiration=28800000 -jwt.refresh.expiration=604800000 +jwt.refresh.expiration=7776000000 # local env From 16dc5af4306f3f1bfc52041943fd810323a8ff54 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 26 Jun 2026 15:29:24 +0530 Subject: [PATCH 15/18] fix: restore remaining-attempts lockout message from PR #431 Saurav's PR #431 added a "Remaining attempts: N" message on failed login to warn users before account lockout. Port this into the refactored handleFailedLoginAttempt helper so both userAuthenticate and superUserAuthenticate keep the behavior after merging release-3.8.1's account-lock refactor into release-3.8.2. --- .../common/service/users/IEMRAdminUserServiceImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index d8d4abe8..6b5dabf6 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -308,7 +308,13 @@ private void handleFailedLoginAttempt(User user, int failedAttemptThreshold) thr user.setFailedAttempt(currentAttempts + 1); iEMRUserRepositoryCustom.save(user); logger.warn("User Password Wrong"); - throw new IEMRException("Invalid username or password"); + int remainingAttempts = failedAttemptThreshold - (currentAttempts + 1); + if (remainingAttempts == 1) { + throw new IEMRException( + "Invalid username or password. Remaining attempts: 1. " + + "If you enter wrong username or password again, your account will be locked."); + } + throw new IEMRException("Invalid username or password. Remaining attempts: " + remainingAttempts); } else { java.sql.Timestamp lockTime = new java.sql.Timestamp(System.currentTimeMillis()); user.setFailedAttempt(currentAttempts + 1); From 865c266dca92ad24829421c46bd2c1d494cc59ac Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 26 Jun 2026 16:04:16 +0530 Subject: [PATCH 16/18] fix: restore account-lockout message from PR #432 The merge of release-3.8.1's account-lock refactor into release-3.8.2 replaced Saurav Mishra's inline multiple-login-attempt logic (PR #426/#431/#432) with the refactored handlePasswordValidationAndLocking helper. The refactor kept the "Remaining attempts" warning but dropped the final lockout message text, falling back to the unrelated generateLockoutErrorMessage used for already-locked accounts. Restore the exact lockout message from PR #432 so the refactored helper preserves both branches' intended behavior. --- .../iemr/common/service/users/IEMRAdminUserServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 6b5dabf6..e411b9ef 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -323,7 +323,8 @@ private void handleFailedLoginAttempt(User user, int failedAttemptThreshold) thr iEMRUserRepositoryCustom.save(user); logger.warn("User Account has been locked after reaching the limit of {} failed login attempts.", failedAttemptThreshold); - throw new IEMRException(generateLockoutErrorMessage(lockTime)); + throw new IEMRException( + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); } } From d2efa54b304f77ba23757a3abdd0e795c8d0372c Mon Sep 17 00:00:00 2001 From: SnehaRH <77656297+snehar-nd@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:04:10 +0530 Subject: [PATCH 17/18] fix: AMM-2343 on lock/Unlock the api was returning 500 error (#437) Co-authored-by: Sneha --- .../java/com/iemr/common/utils/JwtUserIdValidationFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java index 557d5da5..3be36bbf 100644 --- a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java @@ -168,7 +168,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo logger.info("Validating JWT token from cookie"); if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromCookie)) { AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper( - request, ""); + request, authHeader != null ? authHeader : ""); filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse); return; } @@ -176,7 +176,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo logger.info("Validating JWT token from header"); if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromHeader)) { AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper( - request, ""); + request, authHeader != null ? authHeader : ""); filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse); return; } From 01a5f5fb376f5d3a9c6ac6689f52c0d6e36904e1 Mon Sep 17 00:00:00 2001 From: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:57:03 +0530 Subject: [PATCH 18/18] Merge StopTB-related fixes from release-3.8.3 into release-3.8.2 (#442) * fix(health): stop reporting MySQL as DOWN when SELECT 1 succeeds checkDatabaseConnectivity() was deriving the status field from the background diagnostic severity (pool usage, long transactions, deadlocks) even after the live SELECT 1 connectivity check succeeded - so a CRITICAL finding from the background scan (e.g. connection pool >95% full) caused /common-api/health to report mysql.status=DOWN despite the database being fully reachable and serving real queries (confirmed: login worked fine while health reported DOWN). status now reflects connectivity only (UP if SELECT 1 succeeds, DOWN only on an actual connection failure); severity continues to surface the background diagnostic findings independently, so capacity/performance warnings are still visible without being misreported as an outage. Removed resolveDatabaseStatus() and the now-unused STATUS_DEGRADED constant, both made dead by this change. Co-Authored-By: Claude Sonnet 4.6 * feat(beneficiary): enhance GPS data handling in beneficiary demographics and address flows * Update `gpsTimestamp` in `BeneficiaryDemographicsModel` and `Address` from `Timestamp` to `Long` to support epoch-millisecond values received from clients. * Add missing GPS-related fields (`latitude`, `longitude`, `digipin`, `isGpsUnavailable`, and `gpsUnavailableReason`) to the `Address` DTO for complete GPS data transfer. * Extend mapper decorators (`BenCompleteDetailMapperDecorator`, `CommonIdentityMapperDecorator`, and `IdentityBenEditMapperDecorator`) to propagate GPS information across beneficiary and identity workflows. * fix: add missing $ to resolve GEN_BENEFICIARY_IDS_API_URL in docker properties Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Sehjot Singh Pannu --- src/main/environment/common_docker.properties | 2 +- .../java/com/iemr/common/dto/identity/Address.java | 12 ++++++++++++ .../mapper/BenCompleteDetailMapperDecorator.java | 12 ++++++++++++ .../mapper/CommonIdentityMapperDecorator.java | 6 ++++++ .../mapper/IdentityBenEditMapperDecorator.java | 6 ++++++ .../beneficiary/BeneficiaryDemographicsModel.java | 13 +++++++++++++ .../iemr/common/service/health/HealthService.java | 14 ++++---------- 7 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 064090b3..aca7345a 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -218,4 +218,4 @@ otp.ratelimit.enabled=${OTP_RATELIMIT_ENABLED} otp.ratelimit.minute-limit=${OTP_RATELIMIT_MINUTE_LIMIT} otp.ratelimit.hour-limit=${OTP_RATELIMIT_HOUR_LIMIT} otp.ratelimit.day-limit=${OTP_RATELIMIT_DAY_LIMIT} -generateBeneficiaryIDs-api-url={GEN_BENEFICIARY_IDS_API_URL} +generateBeneficiaryIDs-api-url=${GEN_BENEFICIARY_IDS_API_URL} diff --git a/src/main/java/com/iemr/common/dto/identity/Address.java b/src/main/java/com/iemr/common/dto/identity/Address.java index 7a5e563f..b857f21d 100644 --- a/src/main/java/com/iemr/common/dto/identity/Address.java +++ b/src/main/java/com/iemr/common/dto/identity/Address.java @@ -52,6 +52,12 @@ private String parkingPlaceName; private Integer servicePointID; private String servicePointName; + private Double gpsLatitude; + private Double gpsLongitude; + private String digipin; + private Long gpsTimestamp; + private Boolean isGpsUnavailable; + private String gpsUnavailableReason; public static Address bendemographicsAddressMapper(BeneficiaryDemographicsModel i_bendemographics) { Address address = new Address(); @@ -87,6 +93,12 @@ public static Address bendemographicsAddressMapper(BeneficiaryDemographicsModel address.setSubDistrictId(i_bendemographics.getBlockID()); address.setVillageId(i_bendemographics.getDistrictBranchID()); address.setPinCode(i_bendemographics.getPinCode()); + address.setGpsLatitude(i_bendemographics.getLatitude()); + address.setGpsLongitude(i_bendemographics.getLongitude()); + address.setDigipin(i_bendemographics.getDigipin()); + address.setGpsTimestamp(i_bendemographics.getGpsTimestamp()); + address.setIsGpsUnavailable(i_bendemographics.getIsGpsUnavailable()); + address.setGpsUnavailableReason(i_bendemographics.getGpsUnavailableReason()); return address; } diff --git a/src/main/java/com/iemr/common/mapper/BenCompleteDetailMapperDecorator.java b/src/main/java/com/iemr/common/mapper/BenCompleteDetailMapperDecorator.java index 7d263b25..8b29ec15 100644 --- a/src/main/java/com/iemr/common/mapper/BenCompleteDetailMapperDecorator.java +++ b/src/main/java/com/iemr/common/mapper/BenCompleteDetailMapperDecorator.java @@ -158,6 +158,12 @@ public BeneficiaryDemographicsModel createBenDemographicsModel(BeneficiariesDTO demographicsModel.setServicePointID(address.getServicePointID()); demographicsModel.setServicePointName(address.getServicePointName()); demographicsModel.setHabitation(address.getHabitation()); + demographicsModel.setLatitude(address.getGpsLatitude()); + demographicsModel.setLongitude(address.getGpsLongitude()); + demographicsModel.setDigipin(address.getDigipin()); + demographicsModel.setGpsTimestamp(address.getGpsTimestamp()); + demographicsModel.setIsGpsUnavailable(address.getIsGpsUnavailable()); + demographicsModel.setGpsUnavailableReason(address.getGpsUnavailableReason()); } demographicsModel.setHealthCareWorkerID(benDetails.getHealthCareWorkerId()); demographicsModel.setPreferredLangID(beneficiariesDTO.getBeneficiaryDetails().getPreferredLanguageId()); @@ -216,6 +222,12 @@ protected Address beneficiaryDemographicsModelToAddress(BeneficiaryDemographicsM address.setSubDistrict( blockMapper.districtBlockToModelByID(beneficiaryDemographicsModel.getBlockID()).getBlockName()); } + address.setGpsLatitude(beneficiaryDemographicsModel.getLatitude()); + address.setGpsLongitude(beneficiaryDemographicsModel.getLongitude()); + address.setDigipin(beneficiaryDemographicsModel.getDigipin()); + address.setGpsTimestamp(beneficiaryDemographicsModel.getGpsTimestamp()); + address.setIsGpsUnavailable(beneficiaryDemographicsModel.getIsGpsUnavailable()); + address.setGpsUnavailableReason(beneficiaryDemographicsModel.getGpsUnavailableReason()); return address; } diff --git a/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java b/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java index 1480f306..f18920ad 100644 --- a/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java +++ b/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java @@ -248,6 +248,12 @@ protected Address beneficiaryDemographicsModelToAddress(BeneficiaryDemographicsM address.setSubDistrict( blockMapper.districtBlockToModelByID(beneficiaryDemographicsModel.getBlockID()).getBlockName()); } + address.setGpsLatitude(beneficiaryDemographicsModel.getLatitude()); + address.setGpsLongitude(beneficiaryDemographicsModel.getLongitude()); + address.setDigipin(beneficiaryDemographicsModel.getDigipin()); + address.setGpsTimestamp(beneficiaryDemographicsModel.getGpsTimestamp()); + address.setIsGpsUnavailable(beneficiaryDemographicsModel.getIsGpsUnavailable()); + address.setGpsUnavailableReason(beneficiaryDemographicsModel.getGpsUnavailableReason()); return address; } diff --git a/src/main/java/com/iemr/common/mapper/IdentityBenEditMapperDecorator.java b/src/main/java/com/iemr/common/mapper/IdentityBenEditMapperDecorator.java index e0f3ef1c..27b4a602 100644 --- a/src/main/java/com/iemr/common/mapper/IdentityBenEditMapperDecorator.java +++ b/src/main/java/com/iemr/common/mapper/IdentityBenEditMapperDecorator.java @@ -107,6 +107,12 @@ protected Address beneficiaryDemographicsModelToAddress(BeneficiaryDemographicsM address.setSubDistrict( blockMapper.districtBlockToModelByID(beneficiaryDemographicsModel.getBlockID()).getBlockName()); } + address.setGpsLatitude(beneficiaryDemographicsModel.getLatitude()); + address.setGpsLongitude(beneficiaryDemographicsModel.getLongitude()); + address.setDigipin(beneficiaryDemographicsModel.getDigipin()); + address.setGpsTimestamp(beneficiaryDemographicsModel.getGpsTimestamp()); + address.setIsGpsUnavailable(beneficiaryDemographicsModel.getIsGpsUnavailable()); + address.setGpsUnavailableReason(beneficiaryDemographicsModel.getGpsUnavailableReason()); return address; } diff --git a/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryDemographicsModel.java b/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryDemographicsModel.java index 83450844..044650ea 100644 --- a/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryDemographicsModel.java +++ b/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryDemographicsModel.java @@ -173,6 +173,19 @@ public class BeneficiaryDemographicsModel { @Expose private String habitation; + @Expose + private Double latitude; + @Expose + private Double longitude; + @Expose + private String digipin; + @Expose + private Long gpsTimestamp; + @Expose + private Boolean isGpsUnavailable = false; + @Expose + private String gpsUnavailableReason; + @Expose private String occupation; @Expose diff --git a/src/main/java/com/iemr/common/service/health/HealthService.java b/src/main/java/com/iemr/common/service/health/HealthService.java index 7714efce..0c905d0a 100644 --- a/src/main/java/com/iemr/common/service/health/HealthService.java +++ b/src/main/java/com/iemr/common/service/health/HealthService.java @@ -72,7 +72,6 @@ public class HealthService { private static final String STATUS_VALUE = "Value"; private static final String STATUS_UP = "UP"; private static final String STATUS_DOWN = "DOWN"; - private static final String STATUS_DEGRADED = "DEGRADED"; private static final String STATUS_NOT_CONFIGURED = "NOT_CONFIGURED"; // Thresholds @@ -183,9 +182,11 @@ private Map checkDatabaseConnectivity() { stmt.setQueryTimeout(3); // Bounds only the SELECT 1 execution stmt.execute("SELECT 1"); - // If SELECT 1 succeeds, use cached severity from background diagnostics + // SELECT 1 succeeded, so connectivity is UP regardless of background severity. + // Severity is a separate capacity/performance signal (pool usage, long transactions, + // etc.) — it must not be reported as a connectivity outage. String severity = cachedDbSeverity.get(); - result.put(FIELD_STATUS, resolveDatabaseStatus(severity)); + result.put(FIELD_STATUS, STATUS_UP); result.put(FIELD_SEVERITY, severity); } catch (Exception e) { @@ -402,13 +403,6 @@ private String performConnectionUsageCheck(Connection conn) { } return SEVERITY_OK; } - private String resolveDatabaseStatus(String severity) { - return switch (severity) { - case SEVERITY_CRITICAL -> STATUS_DOWN; - case SEVERITY_WARNING -> STATUS_DEGRADED; - default -> STATUS_UP; - }; - } private String escalate(String current, String candidate) { return severityRank(candidate) > severityRank(current) ? candidate : current; }