Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3c5b5b8
add bengali language translation on dynamic forms
SauravBizbRolly May 6, 2026
eb9de55
Merge pull request #406 from PSMRI/feature/add_bengali_language
SauravBizbRolly May 6, 2026
2cd890c
Merge remote-tracking branch 'upstream/vbb/release-3.8.1' into sm/rel…
SauravBizbRolly May 12, 2026
bc54def
change api version
SauravBizbRolly May 12, 2026
3e0bbb7
Sn/3.8.1 (#423)
snehar-nd May 22, 2026
cbb1817
fix: use stringRedisTemplate for jti: key read/delete in concurrent s…
vishwab1 May 22, 2026
8b548d3
fix: correct exempt roles and allow superadmin concurrent sessions
vishwab1 May 22, 2026
a8102b1
Merge pull request #424 from PSMRI/vb/fixSession
snehar-nd May 22, 2026
2c23d93
Implement feature multiple login attempt
SauravBizbRolly May 29, 2026
fe3336a
Implement feature multiple login attempt
SauravBizbRolly May 29, 2026
5424887
Merge pull request #426 from PSMRI/feature/handel_multiple_attempt_pa…
SauravBizbRolly Jun 4, 2026
a6a6d3c
fix: delete camp:vanID and camp:parkingPlaceID from Redis on MMU logout
vishwab1 Jun 12, 2026
758bf36
feat: add endpoint to expose server's LAN IP address
vishwab1 Jun 16, 2026
be92d7c
fix: extented the token expiry timing
snehar-nd Jun 16, 2026
fc1deba
Merge pull request #428 from PSMRI/sn/tokenExpiry
snehar-nd Jun 16, 2026
e04714b
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
fee73b3
Merge pull request #431 from PSMRI/fix_multiple_login_issue
SauravBizbRolly Jun 22, 2026
818257c
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
a27db85
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
38e4c02
Merge pull request #432 from PSMRI/fix_login_issue_multiple_login
SauravBizbRolly Jun 22, 2026
73caab7
Merge remote-tracking branch 'origin/main' into vb/release-3.8.2
vishwab1 Jun 25, 2026
d08403f
Merge remote-tracking branch 'origin/release-3.8.1' into vb/release-3…
vishwab1 Jun 25, 2026
5a34772
Merge remote-tracking branch 'origin/release-3.8.2' into vb/release-3…
vishwab1 Jun 25, 2026
c9a2694
Merge pull request #434 from PSMRI/vb/release-3.8.2
snehar-nd Jun 25, 2026
c3013e0
Increase timing of refresh token (#435)
SauravBizbRolly Jun 26, 2026
16dc5af
fix: restore remaining-attempts lockout message from PR #431
vishwab1 Jun 26, 2026
8743f34
Merge remote-tracking branch 'origin/release-3.8.2' into vb/release-3…
vishwab1 Jun 26, 2026
865c266
fix: restore account-lockout message from PR #432
vishwab1 Jun 26, 2026
89d1da2
Merge pull request #436 from PSMRI/vb/release-3.8.2
SauravBizbRolly Jun 26, 2026
d2efa54
fix: AMM-2343 on lock/Unlock the api was returning 500 error (#437)
snehar-nd Jun 29, 2026
01a5f5f
Merge StopTB-related fixes from release-3.8.3 into release-3.8.2 (#442)
vishwab1 Jul 22, 2026
d5c079c
save fcm user token in table
SauravBizbRolly Jul 23, 2026
e9b38a2
save fcm user token in table
SauravBizbRolly Jul 23, 2026
3714b6d
save fcm user token in table
SauravBizbRolly Jul 23, 2026
251cf6f
save fcm user token in table
SauravBizbRolly Jul 23, 2026
4ef61c2
save fcm user token in table
SauravBizbRolly Jul 23, 2026
65c4f93
save fcm user token in table
SauravBizbRolly Jul 23, 2026
ac35624
save fcm user token in table
SauravBizbRolly Jul 24, 2026
4f58051
save fcm user token in table
SauravBizbRolly Jul 24, 2026
7b4b14f
save fcm user token in table
SauravBizbRolly Jul 24, 2026
865ede1
save fcm user token in table
SauravBizbRolly Jul 24, 2026
c4cfb5d
save fcm user token in table
SauravBizbRolly Jul 24, 2026
3e31eda
save fcm user token in table
SauravBizbRolly Jul 24, 2026
700e2e2
save fcm user token in table
SauravBizbRolly Jul 24, 2026
e1bde6e
Merge pull request #443 from PSMRI/feature/save_user_fcm_token
SauravBizbRolly Jul 27, 2026
27662f6
fix(stoptb): resolve district/block/village against Nikshay masters, …
vishwab1 Jul 31, 2026
88a1073
chore(stoptb): remove camp:vanID/parkingPlaceID Redis delete on logout
vishwab1 Aug 9, 2026
49c7924
Merge pull request #446 from PSMRI/vb/release-3.8.2-van-env-fix
SauravBizbRolly Aug 9, 2026
54080cc
fix order issue in dynamic from (#447)
SauravBizbRolly Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.iemr.common-API</groupId>
<artifactId>common-api</artifactId>
<version>3.8.1</version>
<version>3.8.2</version>
<packaging>war</packaging>

<name>Common-API</name>
Expand Down
2 changes: 1 addition & 1 deletion src/main/environment/common_docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@
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;


@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;
Expand All @@ -42,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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Map<String, Object>> getConnectInfo() {
Map<String, Object> response = new LinkedHashMap<>();
response.put("ip", NetworkUtil.getLanIPAddress());
response.put("port", serverPort);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,17 +44,60 @@ public class FirebaseNotificationController {
@Autowired
FirebaseNotificationService firebaseNotificationService;

@RequestMapping(value = "sendNotification",method = RequestMethod.POST,headers = "Authorization")
public String sendNotificationByToken(@RequestBody NotificationMessage notificationMessage){
return firebaseNotificationService.sendNotification(notificationMessage);

@PostMapping("/sendNotification")
public ResponseEntity<String> 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");
}
}

@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public class IEMRAdminController {
private static final String USER_ID_FIELD = "userId";
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
private InputMapper inputMapper = new InputMapper();
private static final Set<String> CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("provideradmin", "superadmin");

// @Value("${captcha.enable-captcha}")
private boolean enableCaptcha =false;
Expand Down Expand Up @@ -172,7 +173,17 @@ public String userAuthenticate(
}

String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword());
List<User> mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword);


List<User> mUser = iemrAdminUserServiceImpl
.userAuthenticate(m_User.getUserName(), decryptPassword);


User loggedInUser = mUser.get(0);

loggedInUser.setFailedAttempt(0);

iemrAdminUserServiceImpl.save(loggedInUser);
JSONObject resMap = new JSONObject();
JSONObject serviceRoleMultiMap = new JSONObject();
JSONObject serviceRoleMap = new JSONObject();
Expand All @@ -181,11 +192,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());
Expand Down Expand Up @@ -254,7 +276,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()) {
Expand Down Expand Up @@ -413,16 +434,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 = stringRedisTemplate.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]);
}
stringRedisTemplate.delete("jti:" + usernameKey);
}
stringRedisTemplate.delete("jti:" + usernameKey);
}

response.setResponse("User successfully logged out");
Expand Down Expand Up @@ -537,11 +570,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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/iemr/common/data/location/NikshayDistrict.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.data.location;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

/**
* Nikshay's own district master (m_nikshay_district) β€” an isolated ID space
* from AMRIT's standard m_district, used only for Stop TB location mapping.
*/
@Entity
@Table(name = "m_nikshay_district")
@Data
public class NikshayDistrict {
@Id
@Column(name = "NikshayDistrictID")
private Integer nikshayDistrictID;

@Column(name = "DistrictName")
private String districtName;
}
Loading
Loading