From 3c5b5b895cd4bc6d1bdddfce596ce9f6054ea0e2 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Wed, 6 May 2026 12:18:05 +0530 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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; } From d5c079c24f5c8f63beca1a636db30605cd754679 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 16:23:01 +0530 Subject: [PATCH 19/31] save fcm user token in table --- .../common/data/userToken/UserTokenData.java | 17 +++++++++-------- .../model/notification/NotificationMessage.java | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/iemr/common/data/userToken/UserTokenData.java b/src/main/java/com/iemr/common/data/userToken/UserTokenData.java index 0646972e..9a6da29d 100644 --- a/src/main/java/com/iemr/common/data/userToken/UserTokenData.java +++ b/src/main/java/com/iemr/common/data/userToken/UserTokenData.java @@ -24,23 +24,24 @@ */ package com.iemr.common.data.userToken; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import java.sql.Timestamp; +import static jakarta.persistence.GenerationType.IDENTITY; + @Entity -@Table(name = "user_tokens", schema = "db_iemr") +@Table(name = "user_fcm_tokens", schema = "db_iemr") @Data public class UserTokenData { @Id + @GeneratedValue(strategy = IDENTITY) + private int id; @Column(name = "user_id") - Integer userId; + private Integer userId; @Column(name = "token") - String token; + private String token; @Column(name = "updated_at") - Timestamp updatedAt; + private Timestamp updatedAt; } diff --git a/src/main/java/com/iemr/common/model/notification/NotificationMessage.java b/src/main/java/com/iemr/common/model/notification/NotificationMessage.java index 58343034..55cb0e62 100644 --- a/src/main/java/com/iemr/common/model/notification/NotificationMessage.java +++ b/src/main/java/com/iemr/common/model/notification/NotificationMessage.java @@ -35,4 +35,4 @@ public class NotificationMessage { private String title; private String body; private Map data; -} +} \ No newline at end of file From e9b38a27ae04c030c644d2f4524b829ea22eef13 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 16:47:02 +0530 Subject: [PATCH 20/31] save fcm user token in table --- .../java/com/iemr/common/repo/userToken/UserTokenRepo.java | 3 +++ .../firebaseNotification/FirebaseNotificationService.java | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java b/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java index 817f26f0..10e504ae 100644 --- a/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java +++ b/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java @@ -26,6 +26,9 @@ import com.iemr.common.data.userToken.UserTokenData; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository public interface UserTokenRepo extends JpaRepository { } diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 7f33c453..01de9df3 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -94,12 +94,10 @@ public String updateToken(UserToken userToken) { if (existingTokenData.isPresent()) { userTokenData = existingTokenData.get(); userTokenData.setToken(userToken.getToken()); - userTokenData.setUpdatedAt(new Timestamp(System.currentTimeMillis())); } else { userTokenData = new UserTokenData(); userTokenData.setUserId(userToken.getUserId()); userTokenData.setToken(userToken.getToken()); - userTokenData.setUpdatedAt(new Timestamp(System.currentTimeMillis())); } userTokenRepo.save(userTokenData); From 3714b6d51436453305aa29532e0e05ce2aba5ebb Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 16:53:15 +0530 Subject: [PATCH 21/31] save fcm user token in table --- .../{UserTokenData.java => UserFcmTokenData.java} | 2 +- .../{UserTokenRepo.java => UserFcmTokenRepo.java} | 4 ++-- .../FirebaseNotificationService.java | 15 +++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) rename src/main/java/com/iemr/common/data/userToken/{UserTokenData.java => UserFcmTokenData.java} (97%) rename src/main/java/com/iemr/common/repo/userToken/{UserTokenRepo.java => UserFcmTokenRepo.java} (88%) diff --git a/src/main/java/com/iemr/common/data/userToken/UserTokenData.java b/src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java similarity index 97% rename from src/main/java/com/iemr/common/data/userToken/UserTokenData.java rename to src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java index 9a6da29d..0a0fefbe 100644 --- a/src/main/java/com/iemr/common/data/userToken/UserTokenData.java +++ b/src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java @@ -34,7 +34,7 @@ @Entity @Table(name = "user_fcm_tokens", schema = "db_iemr") @Data -public class UserTokenData { +public class UserFcmTokenData { @Id @GeneratedValue(strategy = IDENTITY) private int id; diff --git a/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java b/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java similarity index 88% rename from src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java rename to src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java index 10e504ae..ccc1d6d9 100644 --- a/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java +++ b/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java @@ -24,11 +24,11 @@ */ package com.iemr.common.repo.userToken; -import com.iemr.common.data.userToken.UserTokenData; +import com.iemr.common.data.userToken.UserFcmTokenData; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface UserTokenRepo extends JpaRepository { +public interface UserFcmTokenRepo extends JpaRepository { } diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 01de9df3..9514620f 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -28,10 +28,10 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; -import com.iemr.common.data.userToken.UserTokenData; +import com.iemr.common.data.userToken.UserFcmTokenData; import com.iemr.common.model.notification.NotificationMessage; import com.iemr.common.model.notification.UserToken; -import com.iemr.common.repo.userToken.UserTokenRepo; +import com.iemr.common.repo.userToken.UserFcmTokenRepo; import com.iemr.common.utils.CookieUtil; import com.iemr.common.utils.JwtUtil; import com.iemr.common.utils.exception.IEMRException; @@ -43,7 +43,6 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import java.sql.Timestamp; import java.util.Optional; @Service @@ -54,7 +53,7 @@ public class FirebaseNotificationService { FirebaseMessaging firebaseMessaging; @Autowired - private UserTokenRepo userTokenRepo; + private UserFcmTokenRepo userTokenRepo; @Autowired private CookieUtil cookieUtil; @@ -87,15 +86,15 @@ public String sendNotification(NotificationMessage notificationMessage) { } public String updateToken(UserToken userToken) { - Optional existingTokenData = userTokenRepo.findById(userToken.getUserId()); + Optional existingTokenData = userTokenRepo.findById(userToken.getUserId()); - UserTokenData userTokenData; + UserFcmTokenData userTokenData; if (existingTokenData.isPresent()) { userTokenData = existingTokenData.get(); userTokenData.setToken(userToken.getToken()); } else { - userTokenData = new UserTokenData(); + userTokenData = new UserFcmTokenData(); userTokenData.setUserId(userToken.getUserId()); userTokenData.setToken(userToken.getToken()); } @@ -109,7 +108,7 @@ public String getUserToken() throws IEMRException { .getRequest(); String jwtTokenFromCookie = cookieUtil.getJwtTokenFromCookie(requestHeader); return userTokenRepo.findById(Integer.parseInt(jwtUtil.getUserIdFromToken(jwtTokenFromCookie))) // because your userId is Long in DB - .map(UserTokenData::getToken) + .map(UserFcmTokenData::getToken) .orElse(null); // } From 251cf6fa361d34032ea35b97b22b47cfb8ab2d66 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 17:01:57 +0530 Subject: [PATCH 22/31] save fcm user token in table --- .../firebaseNotification/FirebaseNotificationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 9514620f..7dbee27f 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -72,7 +72,7 @@ public String sendNotification(NotificationMessage notificationMessage) { Notification notification = Notification.builder().setTitle(notificationMessage.getTitle()).setBody(notificationMessage.getBody()).build(); - Message message = Message.builder().setTopic(notificationMessage.getToken()).setNotification(notification).putAllData(notificationMessage.getData()).build(); + Message message = Message.builder().setToken(notificationMessage.getToken()).setNotification(notification).putAllData(notificationMessage.getData()).build(); try { From 4ef61c244c4c01aa67e8ddd18e33048962d61195 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 17:38:38 +0530 Subject: [PATCH 23/31] save fcm user token in table --- .../FirebaseNotificationController.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java index 3bb10cfc..e37f2c41 100644 --- a/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java +++ b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java @@ -28,9 +28,12 @@ import com.iemr.common.model.notification.UserToken; import com.iemr.common.service.firebaseNotification.FirebaseNotificationService; import com.iemr.common.utils.exception.IEMRException; +import com.iemr.common.utils.response.ApiResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @@ -41,17 +44,33 @@ public class FirebaseNotificationController { @Autowired FirebaseNotificationService firebaseNotificationService; - @RequestMapping(value = "sendNotification",method = RequestMethod.POST,headers = "Authorization") + @PostMapping(value = "/sendNotification") public String sendNotificationByToken(@RequestBody NotificationMessage notificationMessage){ return firebaseNotificationService.sendNotification(notificationMessage); } - @RequestMapping(value = "updateToken",method = RequestMethod.POST,headers = "Authorization") - public String updateToken(@RequestBody UserToken userToken){ - return firebaseNotificationService.updateToken(userToken); + @PostMapping("/updateToken") + public ResponseEntity updateToken(@RequestBody UserToken userToken) { + try { + + Object result = firebaseNotificationService.updateToken(userToken); + + return ResponseEntity.ok(result); + + } catch (IllegalArgumentException e) { + + return ResponseEntity.badRequest().body(e.getMessage()); + + } catch (Exception e) { + + logger.error("Error while updating Firebase token", e); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Failed to update Firebase token."); + } } - @RequestMapping(value = "getToken",method = RequestMethod.GET,headers = "Authorization") + @PostMapping(value = "getToken") public String getUserToken() throws IEMRException { return firebaseNotificationService.getUserToken(); From 65c4f93d52cdbb9de8815a1e45d4c46822270cfa Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Thu, 23 Jul 2026 17:40:57 +0530 Subject: [PATCH 24/31] save fcm user token in table --- .../java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java | 4 ++++ .../firebaseNotification/FirebaseNotificationService.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java b/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java index ccc1d6d9..b2ed14df 100644 --- a/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java +++ b/src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java @@ -28,7 +28,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserFcmTokenRepo extends JpaRepository { + + Optional findByUserId(Integer userId); } diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 7dbee27f..52591cb4 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -86,7 +86,7 @@ public String sendNotification(NotificationMessage notificationMessage) { } public String updateToken(UserToken userToken) { - Optional existingTokenData = userTokenRepo.findById(userToken.getUserId()); + Optional existingTokenData = userTokenRepo.findByUserId(userToken.getUserId()); UserFcmTokenData userTokenData; From ac356240af02e1d66b79008de48b0221464146f4 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 12:35:13 +0530 Subject: [PATCH 25/31] save fcm user token in table --- .../FirebaseNotificationController.java | 33 +++++++++++++++-- .../FirebaseNotificationService.java | 35 +++++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java index e37f2c41..1cb2753d 100644 --- a/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java +++ b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java @@ -44,9 +44,36 @@ public class FirebaseNotificationController { @Autowired FirebaseNotificationService firebaseNotificationService; - @PostMapping(value = "/sendNotification") - public String sendNotificationByToken(@RequestBody NotificationMessage notificationMessage){ - return firebaseNotificationService.sendNotification(notificationMessage); + + @PostMapping("/sendNotification") + public ResponseEntity sendNotificationByToken(@RequestBody NotificationMessage notificationMessage) { + + logger.info("Received notification request. Token={}, Title={}, ReceiverId={}, Type={}", + notificationMessage.getToken(), + notificationMessage.getTitle(), + notificationMessage.getData() != null ? notificationMessage.getData().get("receiver_user_id") : null, + notificationMessage.getData() != null ? notificationMessage.getData().get("notification_type") : null); + + logger.debug("Notification payload: {}", notificationMessage); + + try { + String response = firebaseNotificationService.sendNotification(notificationMessage); + + logger.info("Notification processed successfully. Response={}", response); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + + logger.error("Failed to process notification request. Token={}, ReceiverId={}, Error={}", + notificationMessage.getToken(), + notificationMessage.getData() != null ? notificationMessage.getData().get("receiver_user_id") : null, + e.getMessage(), + e); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Failed to send notification"); + } } @PostMapping("/updateToken") diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 52591cb4..834ce443 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -26,8 +26,10 @@ import com.google.firebase.FirebaseException; import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; +import com.google.gson.Gson; import com.iemr.common.data.userToken.UserFcmTokenData; import com.iemr.common.model.notification.NotificationMessage; import com.iemr.common.model.notification.UserToken; @@ -65,23 +67,42 @@ public class FirebaseNotificationService { public String sendNotification(NotificationMessage notificationMessage) { + + logger.info("Notification Request : {}", new Gson().toJson(notificationMessage)); + if (firebaseMessaging == null) { - logger.error("⚠️ Firebase is not configured, skipping notification"); - return null; + logger.error("Firebase is not configured"); + return "Firebase is not configured"; } - Notification notification = Notification.builder().setTitle(notificationMessage.getTitle()).setBody(notificationMessage.getBody()).build(); + try { - Message message = Message.builder().setToken(notificationMessage.getToken()).setNotification(notification).putAllData(notificationMessage.getData()).build(); + Notification notification = Notification.builder() + .setTitle(notificationMessage.getTitle()) + .setBody(notificationMessage.getBody()) + .build(); + Message message = Message.builder() + .setToken(notificationMessage.getToken()) + .setNotification(notification) + .putAllData(notificationMessage.getData()) + .build(); - try { String response = FirebaseMessaging.getInstance().send(message); + logger.info("FCM Success : {}", response); + return response; - } catch (FirebaseException e) { - return "Error sending notification"; + } catch (FirebaseMessagingException e) { + + logger.error("FirebaseMessagingException : {}", e.getMessage(), e); + return e.getMessage(); + + } catch (Exception e) { + + logger.error("Unexpected Exception : {}", e.getMessage(), e); + throw e; } } From 4f58051954978a7bdce865ee338ccb2fa0b28b80 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 13:55:32 +0530 Subject: [PATCH 26/31] save fcm user token in table --- .../firebaseNotification/FirebaseNotificationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 834ce443..809eda19 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -30,6 +30,7 @@ import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; import com.google.gson.Gson; +import com.iemr.common.config.firebase.FirebaseMessagingConfig; import com.iemr.common.data.userToken.UserFcmTokenData; import com.iemr.common.model.notification.NotificationMessage; import com.iemr.common.model.notification.UserToken; @@ -51,8 +52,8 @@ public class FirebaseNotificationService { final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); - @Autowired(required = false) - FirebaseMessaging firebaseMessaging; + @Autowired + FirebaseMessagingConfig firebaseMessaging; @Autowired private UserFcmTokenRepo userTokenRepo; From 7b4b14fde2cac43d761eb9b6a92a9275c714c7d5 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 13:58:02 +0530 Subject: [PATCH 27/31] save fcm user token in table --- .../iemr/common/config/firebase/FirebaseMessagingConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java index ebb697ec..baca2bc2 100644 --- a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java +++ b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java @@ -21,10 +21,10 @@ public class FirebaseMessagingConfig { private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); - @Value("${firebase.enabled:false}") + @Value("${firebase.enabled}") private boolean firebaseEnabled; - @Value("${firebase.credential-file:}") + @Value("${firebase.credential-file}") private String firebaseCredentialFile; From 865ede141dee758c3b9091277281cee611a91b5c Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 14:12:34 +0530 Subject: [PATCH 28/31] save fcm user token in table --- .../FirebaseNotificationService.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 809eda19..7d642c74 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -53,7 +53,10 @@ public class FirebaseNotificationService { final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); @Autowired - FirebaseMessagingConfig firebaseMessaging; + FirebaseMessagingConfig firebaseMessagingConfig; + + @Autowired(required = false) + private FirebaseMessaging firebaseMessaging; @Autowired private UserFcmTokenRepo userTokenRepo; @@ -69,11 +72,12 @@ public class FirebaseNotificationService { public String sendNotification(NotificationMessage notificationMessage) { - logger.info("Notification Request : {}", new Gson().toJson(notificationMessage)); + logger.info("========== FCM Notification Request =========="); + logger.info("Request : {}", new Gson().toJson(notificationMessage)); if (firebaseMessaging == null) { - logger.error("Firebase is not configured"); - return "Firebase is not configured"; + logger.error("FirebaseMessaging bean is not initialized."); + return "FirebaseMessaging bean is not initialized."; } try { @@ -89,24 +93,30 @@ public String sendNotification(NotificationMessage notificationMessage) { .putAllData(notificationMessage.getData()) .build(); - String response = FirebaseMessaging.getInstance().send(message); + logger.info("Sending notification to token: {}", notificationMessage.getToken()); + + String response = firebaseMessaging.send(message); - logger.info("FCM Success : {}", response); + logger.info("Notification sent successfully."); + logger.info("Firebase Message Id : {}", response); return response; } catch (FirebaseMessagingException e) { - logger.error("FirebaseMessagingException : {}", e.getMessage(), e); - return e.getMessage(); + logger.error("FirebaseMessagingException"); + logger.error("Error Code : {}", e.getMessagingErrorCode()); + logger.error("Error Message : {}", e.getMessage(), e); + + return "FCM Error : " + e.getMessage(); } catch (Exception e) { - logger.error("Unexpected Exception : {}", e.getMessage(), e); - throw e; + logger.error("Unexpected exception while sending notification.", e); + + return "Unexpected Error : " + e.getMessage(); } } - public String updateToken(UserToken userToken) { Optional existingTokenData = userTokenRepo.findByUserId(userToken.getUserId()); From c4cfb5d1907b608c11db4086fb0f6cd05b1b09cf Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 14:25:22 +0530 Subject: [PATCH 29/31] save fcm user token in table --- .../iemr/common/config/firebase/FirebaseMessagingConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java index baca2bc2..7aae4c89 100644 --- a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java +++ b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java @@ -31,6 +31,9 @@ public class FirebaseMessagingConfig { @Bean @ConditionalOnProperty(name = "firebase.enabled", havingValue = "true") public FirebaseMessaging firebaseMessaging() throws IOException { + logger.info("===== Initializing Firebase ====="); + logger.info("firebaseEnabled={}", firebaseEnabled); + logger.info("firebaseCredentialFile={}", firebaseCredentialFile); if (!firebaseEnabled) { logger.error("⚠️ Firebase disabled by config"); return null; From 3e31edae6f0b99de5170cf570c26d14e01a5aa48 Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 15:30:42 +0530 Subject: [PATCH 30/31] save fcm user token in table --- .../FirebaseNotificationService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 7d642c74..691318db 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -69,9 +70,20 @@ public class FirebaseNotificationService { private Message message; + @Value("${firebase.enabled}") + private boolean firebaseEnabled; + + @Value("${firebase.credential-file}") + private String firebaseCredentialFile; + + public String sendNotification(NotificationMessage notificationMessage) { + logger.info("===== Initializing Firebase ====="); + logger.info("firebaseEnabled={}", firebaseEnabled); + logger.info("firebaseCredentialFile={}", firebaseCredentialFile); + logger.info("========== FCM Notification Request =========="); logger.info("Request : {}", new Gson().toJson(notificationMessage)); From 700e2e2010613a3391308668e4729827f0b1da5d Mon Sep 17 00:00:00 2001 From: Saurav Mishra Date: Fri, 24 Jul 2026 15:41:54 +0530 Subject: [PATCH 31/31] save fcm user token in table --- .../common/config/firebase/FirebaseMessagingConfig.java | 8 +++++--- .../firebaseNotification/FirebaseNotificationService.java | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java index 7aae4c89..08d9a518 100644 --- a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java +++ b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java @@ -45,9 +45,11 @@ public FirebaseMessaging firebaseMessaging() throws IOException { return null; // don't throw, app will still start } - GoogleCredentials credentials = GoogleCredentials.fromStream( - new ClassPathResource(firebaseCredentialFile).getInputStream() - ); + GoogleCredentials credentials = + GoogleCredentials.fromStream( + new FileInputStream(firebaseCredentialFile) + ); + FirebaseOptions options = FirebaseOptions.builder() .setCredentials(credentials) .build(); diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java index 691318db..66140ccc 100644 --- a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -53,9 +53,6 @@ public class FirebaseNotificationService { final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); - @Autowired - FirebaseMessagingConfig firebaseMessagingConfig; - @Autowired(required = false) private FirebaseMessaging firebaseMessaging;