diff --git a/pom.xml b/pom.xml index 62593f28..11ad9f37 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iemr.common-API common-api - 3.4.0 + 3.6.0 war Common-API @@ -111,6 +111,12 @@ + + + com.google.firebase + firebase-admin + 9.4.3 + org.springframework.boot spring-boot-starter-data-jpa @@ -257,7 +263,7 @@ logback-core test - + org.springdoc springdoc-openapi-starter-webmvc-ui diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index a5e66660..0184b32f 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -32,6 +32,7 @@ identity-1097-api-url = @env.IDENTITY_1097_API_URL@ send-sms=@env.SEND_SMS@ sendSMSUrl = @env.SEND_SMS_URL@ source-address=@env.SMS_SOURCE_ADDRESS@ +sms-consent-source-address = @env.SMS_CONSENT_SOURCE_ADDRESS@ sms-username=@env.SMS_USERNAME@ sms-password=@env.SMS_PASSWORD@ send-message-url=@env.SMS_MESSAGE_URL@ @@ -40,6 +41,11 @@ send-message-url=@env.SMS_MESSAGE_URL@ start-sms-scheduler=true cron-scheduler-sms=0 0/1 * * * ? * +# Firebase Configuration +firebase.enabled=@env.FIREBASE_ENABLE@ +# if using file +firebase.credential-file=@env.FIREBASE_CREDENTIAL@ + #### Email Configuration send-email=@env.SEND_EMAIL@ spring.mail.host=@env.MAIL_HOST@ @@ -167,8 +173,8 @@ grievancePassword = @env.GRIEVANCE_PASSWORD@ grievanceUserAuthenticate = @env.GRIEVANCE_USER_AUTHENTICATE@ grievanceDataSyncDuration = @env.GRIEVANCE_DATA_SYNC_DURATION@ -springdoc.api-docs.enabled=false -springdoc.swagger-ui.enabled=false +springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ +springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ isProduction=@env.IS_PRODUCTION@ @@ -185,4 +191,16 @@ cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ video-call-url=@env.VIDEO_CALL_URL@ jibri.output.path=@env.JIBRI_OUTPUT_PATH@ -video.recording.path=@env.VIDEO_RECORDING_PATH@ \ No newline at end of file +video.recording.path=@env.VIDEO_RECORDING_PATH@ + +platform.feedback.ratelimit.enabled=@env.PLATFORM_FEEDBACK_RATELIMIT_ENABLED@ +platform.feedback.ratelimit.pepper=@env.PLATFORM_FEEDBACK_RATELIMIT_PEPPER@ +platform.feedback.ratelimit.trust-forwarded-for=@env.PLATFORM_FEEDBACK_RATELIMIT_TRUST_FORWARDED_FOR@ +platform.feedback.ratelimit.forwarded-for-header=@env.PLATFORM_FEEDBACK_RATELIMIT_FORWARDED_FOR_HEADER@ +platform.feedback.ratelimit.minute-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_MINUTE_LIMIT@ +platform.feedback.ratelimit.day-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_DAY_LIMIT@ +platform.feedback.ratelimit.user-day-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_USER_DAY_LIMIT@ +platform.feedback.ratelimit.fail-window-minutes=@env.PLATFORM_FEEDBACK_RATELIMIT_FAIL_WINDOW_MINUTES@ +platform.feedback.ratelimit.backoff-minutes=@env.PLATFORM_FEEDBACK_RATELIMIT_BACKOFF_MINUTES@ +generateBeneficiaryIDs-api-url=@env.GEN_BENEFICIARY_IDS_API_URL@ + diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 41881886..a81ea62e 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -32,6 +32,7 @@ identity-1097-api-url = ${IDENTITY_1097_API_URL} send-sms=${SEND_SMS} sendSMSUrl = ${SEND_SMS_URL} source-address=${SMS_SOURCE_ADDRESS} +sms-consent-source-address=${SMS_CONSENT_SOURCE_ADDRESS} sms-username=${SMS_USERNAME} sms-password=${SMS_PASSWORD} send-message-url=${SMS_MESSAGE_URL} @@ -169,8 +170,8 @@ grievancePassword = ${GRIEVANCE_PASSWORD} grievanceUserAuthenticate = ${GRIEVANCE_USER_AUTHENTICATE} grievanceDataSyncDuration = ${GRIEVANCE_DATA_SYNC_DURATION} -springdoc.api-docs.enabled=false -springdoc.swagger-ui.enabled=false +springdoc.api-docs.enabled=${SWAGGER_DOC_ENABLED} +springdoc.swagger-ui.enabled=${SWAGGER_DOC_ENABLED} isProduction=${IS_PRODUCTION} @@ -185,6 +186,24 @@ captcha.enable-captcha=${ENABLE_CAPTCHA} cors.allowed-origins=${CORS_ALLOWED_ORIGINS} +# # Firebase Configuration +firebase.enabled=${FIREBASE_ENABLE} +# # if using file +firebase.credential-file=${FIREBASE_CREDENTIAL} + + video-call-url=${VIDEO_CALL_URL} jibri.output.path={JIBRI_OUTPUT_PATH} -video.recording.path={VIDEO_RECORDING_PATH} \ No newline at end of file +video.recording.path={VIDEO_RECORDING_PATH} + +# Platform Feedback module +platform.feedback.ratelimit.enabled=${PLATFORM_FEEDBACK_RATELIMIT_ENABLED} +platform.feedback.ratelimit.pepper=${PLATFORM_FEEDBACK_RATELIMIT_PEPPER} +platform.feedback.ratelimit.trust-forwarded-for=${PLATFORM_FEEDBACK_RATELIMIT_TRUST_FORWARDED_FOR} +platform.feedback.ratelimit.forwarded-for-header=${PLATFORM_FEEDBACK_RATELIMIT_FORWARDED_FOR_HEADER} +platform.feedback.ratelimit.minute-limit=${PLATFORM_FEEDBACK_RATELIMIT_MINUTE_LIMIT} +platform.feedback.ratelimit.day-limit=${PLATFORM_FEEDBACK_RATELIMIT_DAY_LIMIT} +platform.feedback.ratelimit.user-day-limit=${PLATFORM_FEEDBACK_RATELIMIT_USER_DAY_LIMIT} +platform.feedback.ratelimit.fail-window-minutes=${PLATFORM_FEEDBACK_RATELIMIT_FAIL_WINDOW_MINUTES} +platform.feedback.ratelimit.backoff-minutes=${PLATFORM_FEEDBACK_RATELIMIT_BACKOFF_MINUTES} +generateBeneficiaryIDs-api-url={GEN_BENEFICIARY_IDS_API_URL} diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 09a526dd..aca73ddb 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -208,3 +208,21 @@ captcha.enable-captcha=true cors.allowed-origins=http://localhost:* +# ---Platform Feedback module --- +# Rate limiter OFF locally (no Redis required) +platform.feedback.ratelimit.enabled=true +platform.feedback.ratelimit.pepper=dev-pepper-123 # dummy + +# trust forwarded-for locally is harmless (localhost only) +platform.feedback.ratelimit.trust-forwarded-for=false +platform.feedback.ratelimit.forwarded-for-header=X-Forwarded-For + +# Optional overrides (not needed if disabled) +platform.feedback.ratelimit.minute-limit=10 +platform.feedback.ratelimit.day-limit=100 +platform.feedback.ratelimit.user-day-limit=50 +platform.feedback.ratelimit.fail-window-minutes=5 +platform.feedback.ratelimit.backoff-minutes=15 + +### generate Beneficiary IDs URL +generateBeneficiaryIDs-api-url=/generateBeneficiaryController/generateBeneficiaryIDs diff --git a/src/main/java/com/iemr/common/CommonApplication.java b/src/main/java/com/iemr/common/CommonApplication.java index b9b797b2..e4a59994 100644 --- a/src/main/java/com/iemr/common/CommonApplication.java +++ b/src/main/java/com/iemr/common/CommonApplication.java @@ -29,7 +29,6 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.client.RestTemplate; diff --git a/src/main/java/com/iemr/common/config/CorsConfig.java b/src/main/java/com/iemr/common/config/CorsConfig.java index 2e226b79..fdd9b494 100644 --- a/src/main/java/com/iemr/common/config/CorsConfig.java +++ b/src/main/java/com/iemr/common/config/CorsConfig.java @@ -11,7 +11,6 @@ public class CorsConfig implements WebMvcConfigurer { @Value("${cors.allowed-origins}") private String allowedOrigins; - @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") @@ -19,8 +18,9 @@ public void addCorsMappings(CorsRegistry registry) { Arrays.stream(allowedOrigins.split(",")) .map(String::trim) .toArray(String[]::new)) - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") + .allowedHeaders("Authorization", "Content-Type", "Accept", "Jwttoken", + "serverAuthorization", "ServerAuthorization", "serverauthorization", "Serverauthorization") .exposedHeaders("Authorization", "Jwttoken") .allowCredentials(true) .maxAge(3600); diff --git a/src/main/java/com/iemr/common/config/RedisConfig.java b/src/main/java/com/iemr/common/config/RedisConfig.java index e812b3f9..796ad557 100644 --- a/src/main/java/com/iemr/common/config/RedisConfig.java +++ b/src/main/java/com/iemr/common/config/RedisConfig.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.iemr.common.data.users.User; +import org.springframework.data.redis.core.StringRedisTemplate; @Configuration public class RedisConfig { @@ -57,4 +58,10 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor return template; } + // new bean for rate limiting & counters + @Bean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) { + return new StringRedisTemplate(factory); + } + } diff --git a/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java new file mode 100644 index 00000000..ebb697ec --- /dev/null +++ b/src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java @@ -0,0 +1,65 @@ +package com.iemr.common.config.firebase; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Base64; + +@Configuration +public class FirebaseMessagingConfig { + private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + + @Value("${firebase.enabled:false}") + private boolean firebaseEnabled; + + @Value("${firebase.credential-file:}") + private String firebaseCredentialFile; + + + @Bean + @ConditionalOnProperty(name = "firebase.enabled", havingValue = "true") + public FirebaseMessaging firebaseMessaging() throws IOException { + if (!firebaseEnabled) { + logger.error("⚠️ Firebase disabled by config"); + return null; + } + + try { + if (firebaseCredentialFile == null || firebaseCredentialFile.isBlank()) { + logger.error("⚠️ No Firebase credentials path provided"); + return null; // don't throw, app will still start + } + + GoogleCredentials credentials = GoogleCredentials.fromStream( + new ClassPathResource(firebaseCredentialFile).getInputStream() + ); + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(credentials) + .build(); + + FirebaseApp firebaseApp = FirebaseApp.getApps().isEmpty() + ? FirebaseApp.initializeApp(options) + : FirebaseApp.getInstance(); + + return FirebaseMessaging.getInstance(firebaseApp); + + } catch (Exception e) { + logger.error("⚠️ Firebase init failed: " + e.getMessage()); + return null; // keep app running + } + + } + +} diff --git a/src/main/java/com/iemr/common/config/quartz/ScheduleForGrievanceDataSync.java b/src/main/java/com/iemr/common/config/quartz/ScheduleForGrievanceDataSync.java index f016de3d..314b31ac 100644 --- a/src/main/java/com/iemr/common/config/quartz/ScheduleForGrievanceDataSync.java +++ b/src/main/java/com/iemr/common/config/quartz/ScheduleForGrievanceDataSync.java @@ -37,3 +37,4 @@ public void execute() { } } + diff --git a/src/main/java/com/iemr/common/controller/beneficiary/BeneficiaryRegistrationController.java b/src/main/java/com/iemr/common/controller/beneficiary/BeneficiaryRegistrationController.java index d9b0f06f..8f573d6d 100644 --- a/src/main/java/com/iemr/common/controller/beneficiary/BeneficiaryRegistrationController.java +++ b/src/main/java/com/iemr/common/controller/beneficiary/BeneficiaryRegistrationController.java @@ -103,6 +103,7 @@ public class BeneficiaryRegistrationController { private BeneficiaryOccupationService beneficiaryOccupationService; private GovtIdentityTypeService govtIdentityTypeService; + @Autowired public void setBenRelationshipTypeService(BenRelationshipTypeService benRelationshipTypeService) { this.benRelationshipTypeService = benRelationshipTypeService; diff --git a/src/main/java/com/iemr/common/controller/beneficiaryConsent/BeneficiaryConsentController.java b/src/main/java/com/iemr/common/controller/beneficiaryConsent/BeneficiaryConsentController.java new file mode 100644 index 00000000..77492d89 --- /dev/null +++ b/src/main/java/com/iemr/common/controller/beneficiaryConsent/BeneficiaryConsentController.java @@ -0,0 +1,118 @@ +/* + * 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.beneficiaryConsent; + +import com.iemr.common.data.beneficiaryConsent.BeneficiaryConsentRequest; +import com.iemr.common.service.beneficiaryOTPHandler.BeneficiaryOTPHandler; +import com.iemr.common.utils.mapper.InputMapper; +import com.iemr.common.utils.response.OutputResponse; +import io.lettuce.core.dynamic.annotation.Param; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.ws.rs.core.MediaType; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping(value = { "/beneficiaryConsent" }) +@RestController +public class BeneficiaryConsentController { + final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private BeneficiaryOTPHandler beneficiaryOTPHandler; + + @Operation(summary = "Send Consent") + @RequestMapping(value = "/sendConsent", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON) + public String sendConsent(@Param(value = "{\"mobNo\":\"String\"}") @RequestBody String requestOBJ) { + + OutputResponse response = new OutputResponse(); + + try { + BeneficiaryConsentRequest obj = InputMapper.gson().fromJson(requestOBJ, BeneficiaryConsentRequest.class); + + String success = beneficiaryOTPHandler.sendOTP(obj); // method name unchanged if internal logic still uses 'OTP' + logger.info(success.toString()); + response.setResponse(success); + + + } catch (Exception e) { + response.setError(500, "error : " + e); + } + return response.toString(); + } + + @Operation(summary = "Validate Consent") + @RequestMapping(value = "/validateConsent", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON) + public String validateConsent(@Param(value = "{\"mobNo\":\"String\",\"otp\":\"Integer\"}") @RequestBody String requestOBJ) { + + OutputResponse response = new OutputResponse(); + + try { + BeneficiaryConsentRequest obj = InputMapper.gson().fromJson(requestOBJ, BeneficiaryConsentRequest.class); + + JSONObject responseOBJ = beneficiaryOTPHandler.validateOTP(obj); + if (responseOBJ != null) + response.setResponse(responseOBJ.toString()); + else + response.setError(500, "failure"); + + } catch (Exception e) { + logger.error("error in validating Consent : " + e); + response.setError(500, "error : " + e); + } + return response.toString(); + } + + @Operation(summary = "Resend Consent") + @RequestMapping(value = "/resendConsent", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON) + public String resendConsent(@Param(value = "{\"mobNo\":\"String\"}") @RequestBody String requestOBJ) { + logger.info(requestOBJ.toString()); + + OutputResponse response = new OutputResponse(); + + try { + BeneficiaryConsentRequest obj = InputMapper.gson().fromJson(requestOBJ, BeneficiaryConsentRequest.class); + + String success = beneficiaryOTPHandler.resendOTP(obj); + logger.info(success.toString()); + + if (success.contains("otp")) + response.setResponse(success); + else + response.setError(500, "failure"); + + } catch (Exception e) { + logger.error("error in re-sending Consent : " + e); + response.setError(500, "error : " + e); + } + return response.toString(); + } + + +} + + diff --git a/src/main/java/com/iemr/common/controller/dynamicForm/DynamicFormController.java b/src/main/java/com/iemr/common/controller/dynamicForm/DynamicFormController.java new file mode 100644 index 00000000..30a1bc3f --- /dev/null +++ b/src/main/java/com/iemr/common/controller/dynamicForm/DynamicFormController.java @@ -0,0 +1,100 @@ +package com.iemr.common.controller.dynamicForm; + +import com.iemr.common.dto.dynamicForm.FieldDTO; +import com.iemr.common.dto.dynamicForm.FormDTO; +import com.iemr.common.dto.dynamicForm.ModuleDTO; +import com.iemr.common.service.dynamicForm.FormMasterService; +import com.iemr.common.utils.response.ApiResponse; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.Query; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequestMapping(value = "dynamicForm") +@RestController +public class DynamicFormController { + @Autowired + private FormMasterService formMasterService; + + @PostMapping(value = "createModule") + public ResponseEntity> createModule(@Valid @RequestBody ModuleDTO moduleDTO) { + try { + Object result = formMasterService.createModule(moduleDTO); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Module created successfully", HttpStatus.OK.value(), result)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.error("Invalid module data: " + e.getMessage(), HttpStatus.BAD_REQUEST.value(), null)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to create module", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + } + + @PostMapping(value = "createForm") + public ResponseEntity> createForm(@Valid @RequestBody FormDTO dto) { + try { + Object result = formMasterService.createForm(dto); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Form created successfully", HttpStatus.OK.value(), result)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to create form", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + } + + @PostMapping(value = "createFields") + public ResponseEntity> createField(@Valid @RequestBody List dto) { + try { + Object result = formMasterService.createField(dto); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Fields created successfully", HttpStatus.OK.value(), result)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to create fields", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + } + + @PostMapping(value = "field/update",headers = "Authorization") + public ResponseEntity> updateField(@Valid @RequestBody FieldDTO dto) { + try { + Object result = formMasterService.updateField(dto); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Field updated successfully", HttpStatus.OK.value(), result)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to update field", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + } + + @DeleteMapping(value = "delete/{fieldId}/field",headers = "Authorization") + public ResponseEntity> deleteField(@PathVariable Long fieldId) { + try { + formMasterService.deleteField(fieldId); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Field deleted successfully", HttpStatus.OK.value(), null)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to delete field", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + } + + @GetMapping(value = "form/{formId}/fields") + public ResponseEntity> getStructuredForm(@PathVariable String formId, @RequestParam(name = "lang", defaultValue = "en") String lang) { + try { + Object result = formMasterService.getStructuredFormByFormId(formId,lang); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success("Form structure fetched successfully", HttpStatus.OK.value(), result)); + } catch (Exception e) { + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Failed to fetch form structure:"+e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value(), e)); + } + } + + +} diff --git a/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java new file mode 100644 index 00000000..3bb10cfc --- /dev/null +++ b/src/main/java/com/iemr/common/controller/firebaseNotification/FirebaseNotificationController.java @@ -0,0 +1,61 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.controller.firebaseNotification; + +import com.iemr.common.model.notification.NotificationMessage; +import com.iemr.common.model.notification.UserToken; +import com.iemr.common.service.firebaseNotification.FirebaseNotificationService; +import com.iemr.common.utils.exception.IEMRException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value= "/firebaseNotification",headers = "Authorization") +public class FirebaseNotificationController { + final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + FirebaseNotificationService firebaseNotificationService; + + @RequestMapping(value = "sendNotification",method = RequestMethod.POST,headers = "Authorization") + 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); + } + + @RequestMapping(value = "getToken",method = RequestMethod.GET,headers = "Authorization") + public String getUserToken() throws IEMRException { + + return firebaseNotificationService.getUserToken(); + } + + +} diff --git a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java index 5fd3e281..d02eb3f2 100644 --- a/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java +++ b/src/main/java/com/iemr/common/controller/nhmdashboard/NHMDetailCallReportScheduler.java @@ -181,3 +181,4 @@ private BeneficiaryCall getCallDetail(DetailedCallReport detailedCallReport) { } } + diff --git a/src/main/java/com/iemr/common/controller/platform_feedback/PlatformFeedbackController.java b/src/main/java/com/iemr/common/controller/platform_feedback/PlatformFeedbackController.java new file mode 100644 index 00000000..556561cf --- /dev/null +++ b/src/main/java/com/iemr/common/controller/platform_feedback/PlatformFeedbackController.java @@ -0,0 +1,76 @@ +/* + * 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.platform_feedback; + +import com.iemr.common.dto.platform_feedback.CategoryResponse; +import com.iemr.common.dto.platform_feedback.FeedbackRequest; +import com.iemr.common.dto.platform_feedback.FeedbackResponse; +import com.iemr.common.service.platform_feedback.PlatformFeedbackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.media.Content; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Platform Feedback", description = "Feedback ingestion and category listing for platform-wide feedback") +@RestController +@RequestMapping("/platform-feedback") +@Validated +public class PlatformFeedbackController { + + private final PlatformFeedbackService service; + + public PlatformFeedbackController(PlatformFeedbackService service) { + this.service = service; + } + + @Operation(summary = "Submit feedback (public endpoint)", + description = "Accepts feedback (anonymous or identified). Accepts categoryId or categorySlug; slug is preferred.") + @ApiResponse(responseCode = "201", description = "Feedback accepted") + @ApiResponse(responseCode = "400", description = "Validation or business error", content = @Content) + @PostMapping + public ResponseEntity submit( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Feedback payload") + @Valid @RequestBody FeedbackRequest req) { + FeedbackResponse resp = service.submitFeedback(req); + return ResponseEntity.status(HttpStatus.CREATED).body(resp); + } + + @Operation(summary = "List active categories", + description = "Returns active categories. Optionally filter by serviceLine (frontend convenience).") + @ApiResponse(responseCode = "200", description = "List of categories") + @GetMapping("/categories") + public ResponseEntity> list( + @Parameter(description = "Optional serviceLine to prefer scopes (1097|104|AAM|MMU|TM|ECD)") + @RequestParam(required = false) String serviceLine) { + if (serviceLine == null) serviceLine = "GLOBAL"; + List list = service.listCategories(serviceLine); + return ResponseEntity.ok(list); + } +} diff --git a/src/main/java/com/iemr/common/controller/sms/SMSController.java b/src/main/java/com/iemr/common/controller/sms/SMSController.java index ee985947..9bacf5b1 100644 --- a/src/main/java/com/iemr/common/controller/sms/SMSController.java +++ b/src/main/java/com/iemr/common/controller/sms/SMSController.java @@ -24,6 +24,7 @@ import java.util.Arrays; import javax.ws.rs.core.MediaType; +import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,7 +102,7 @@ public String getFullSMSTemplate( @Operation(summary = "Save SMS template") @PostMapping(value = "/saveSMSTemplate", produces = MediaType.APPLICATION_JSON, headers = "Authorization") public String saveSMSTemplate( - @Param(value = "\"{\\\"createdBy\\\":\\\"String\\\",\\\"providerServiceMapID\\\":\\\"String\\\",\\\"smsParameterMaps\\\":\\\"String\\\",\\\"smsTemplate\\\":\\\"String\\\",\\\"smsTemplateName\\\":\\\"String\\\",\\\"smsTypeID\\\":\\\"Integer\\\"}\"") @RequestBody CreateSMSRequest request, + @Param(value = "\"{\\\"createdBy\\\":\\\"String\\\",\\\"providerServiceMapID\\\":\\\"String\\\",\\\"smsParameterMaps\\\":\\\"String\\\",\\\"smsTemplate\\\":\\\"String\\\",\\\"smsTemplateName\\\":\\\"String\\\",\\\"smsTypeID\\\":\\\"Integer\\\"}\"") @Valid @RequestBody CreateSMSRequest request, HttpServletRequest serverRequest) { OutputResponse response = new OutputResponse(); logger.info("saveSMSTemplate received request"); diff --git a/src/main/java/com/iemr/common/controller/users/EmployeeSignatureController.java b/src/main/java/com/iemr/common/controller/users/EmployeeSignatureController.java index a2156af1..e29d3220 100644 --- a/src/main/java/com/iemr/common/controller/users/EmployeeSignatureController.java +++ b/src/main/java/com/iemr/common/controller/users/EmployeeSignatureController.java @@ -21,22 +21,28 @@ */ package com.iemr.common.controller.users; +import java.nio.charset.StandardCharsets; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.PropertySource; +import org.springframework.http.CacheControl; +import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; import com.google.gson.Gson; import com.iemr.common.data.users.EmployeeSignature; import com.iemr.common.service.users.EmployeeSignatureServiceImpl; -import com.iemr.common.utils.mapper.InputMapper; import com.iemr.common.utils.response.OutputResponse; import io.swagger.v3.oas.annotations.Operation; @@ -51,29 +57,50 @@ public class EmployeeSignatureController { @Autowired EmployeeSignatureServiceImpl employeeSignatureServiceImpl; - private InputMapper inputMapper = new InputMapper(); - private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); @Operation(summary = "Fetch file") @RequestMapping(value = "/{userID}", headers = "Authorization", method = { RequestMethod.GET }) public ResponseEntity fetchFile(@PathVariable("userID") Long userID) throws Exception { - OutputResponse response = new OutputResponse(); logger.debug("File download for userID" + userID); try { - - EmployeeSignature userSignID = employeeSignatureServiceImpl.fetchSignature(userID); - return ResponseEntity.ok().contentType(MediaType.parseMediaType(userSignID.getFileType())) - .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + userSignID.getFileName() + "\"") - .body(userSignID.getSignature()); + EmployeeSignature userSignID = employeeSignatureServiceImpl.fetchActiveSignature(userID); + + if (userSignID == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, + "Active signature not found for userID: " + userID); + } + String originalName = userSignID.getFileName(); + if (originalName == null || originalName.isBlank()) { + originalName = "signature"; + } + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentDisposition( + ContentDisposition.attachment().filename(originalName, StandardCharsets.UTF_8).build()); + responseHeaders.setCacheControl(CacheControl.noStore()); + responseHeaders.add(HttpHeaders.PRAGMA, "no-cache"); + responseHeaders.setExpires(0); + MediaType mediaType; + try { + mediaType = MediaType.parseMediaType(userSignID.getFileType()); + } catch (InvalidMediaTypeException | NullPointerException e) { + mediaType = MediaType.APPLICATION_OCTET_STREAM; + } + + byte[] fileBytes = userSignID.getSignature(); // MUST be byte[] + if (fileBytes == null || fileBytes.length == 0) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Signature not found for userID: " + userID); + } + + return ResponseEntity.ok().headers(responseHeaders).contentType(mediaType).contentLength(fileBytes.length) + .body(fileBytes); } catch (Exception e) { logger.error("File download for userID failed with exception " + e.getMessage(), e); + throw new Exception("Error while downloading file. Please contact administrator.."); } - return ResponseEntity.badRequest().body(new byte[] {}); - } @Operation(summary = "Fetch file from central") 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 28c7e4b9..8bc0e74d 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -1,8 +1,8 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution * -* Copyright (C) "Piramal Swasthya Management and Research Institute" +* Copyright (C) "Piramal Swasthya Management and Research Institute" * * This file is part of AMRIT. * @@ -80,8 +80,8 @@ public class IEMRAdminController { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private InputMapper inputMapper = new InputMapper(); - @Value("${captcha.enable-captcha}") - private boolean enableCaptcha; +// @Value("${captcha.enable-captcha}") + private boolean enableCaptcha =false; @Autowired private CaptchaValidationService captchaValidatorService; @@ -170,8 +170,8 @@ public String userAuthenticate( JSONObject serviceRoleMap = new JSONObject(); JSONArray serviceRoleList = new JSONArray(); JSONObject previlegeObj = new JSONObject(); - if (m_User.getUserName() != null - && (m_User.getDoLogout() == null || !m_User.getDoLogout()) + 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()); @@ -187,7 +187,7 @@ public String userAuthenticate( String refreshToken = null; if (mUser.size() == 1) { jwtToken = jwtUtil.generateToken(m_User.getUserName(), mUser.get(0).getUserID().toString()); - + User user = new User(); // Assuming the Users class exists user.setUserID(mUser.get(0).getUserID()); user.setUserName(mUser.get(0).getUserName()); @@ -278,15 +278,16 @@ public ResponseEntity refreshToken(@RequestBody Map request) // Get user details String userId = claims.get("userId", String.class); User user = iemrAdminUserServiceImpl.getUserById(Long.parseLong(userId)); - + // Validate that the user still exists and is active if (user == null) { logger.warn("Token validation failed: user not found for userId in token."); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized."); } - - if (user.getM_status() == null || !"Active".equalsIgnoreCase(user.getM_status().getStatus())) { - logger.warn("Token validation failed: user account is inactive or not in 'Active' status."); + + if (user.getM_status() == null || !("Active".equalsIgnoreCase(user.getM_status().getStatus()) + || "New".equalsIgnoreCase(user.getM_status().getStatus()))) { + logger.warn("Token validation failed: user account is neither 'Active' nor 'New'."); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized."); } // Generate new tokens @@ -294,7 +295,7 @@ public ResponseEntity refreshToken(@RequestBody Map request) Map tokens = new HashMap<>(); tokens.put("jwtToken", newJwt); - + // Generate and store a new refresh token (token rotation) String newRefreshToken = jwtUtil.generateRefreshToken(user.getUserName(), userId); String newJti = jwtUtil.getJtiFromToken(newRefreshToken); @@ -343,9 +344,10 @@ public String logOutUserFromConcurrentSession( deleteSessionObjectByGettingSessionDetails(previousTokenFromRedis); sessionObject.deleteSessionObject(previousTokenFromRedis); response.setResponse("User successfully logged out"); - } else + } else{ logger.error("Unable to fetch session from redis"); throw new IEMRException("Session error. Please try again later"); + } } } else { throw new IEMRException("Invalid request object"); @@ -360,7 +362,7 @@ public String logOutUserFromConcurrentSession( } /** - * + * * function to return session object against userName */ private String getConcurrentCheckSessionObjectAgainstUser(String userName) { @@ -390,6 +392,7 @@ private void createUserMapping(User mUser, JSONObject resMap, JSONObject service resMap.put("agentPassword", mUser.getAgentPassword()); resMap.put("m_UserLangMappings", new JSONArray(mUser.getM_UserLangMappings().toString())); resMap.put("designationID", mUser.getDesignationID()); + resMap.put("dhistoken",mUser.getDhistoken()); if (mUser.getDesignation() != null) { resMap.put("designation", new JSONObject(mUser.getDesignation().toString())); } @@ -466,7 +469,7 @@ public String superUserAuthenticate( resMap.put("isAuthenticated", /* Boolean.valueOf(true) */true); resMap.put("userName", mUser.getUserName()); jwtToken = jwtUtil.generateToken(m_User.getUserName(), mUser.getUserID().toString()); - + User user = new User(); // Assuming the Users class exists user.setUserID(mUser.getUserID()); user.setUserName(mUser.getUserName()); @@ -561,7 +564,7 @@ public String getLoginResponse(HttpServletRequest request) { if (authHeader.isEmpty()) { // Try JWT token from header first String jwtToken = request.getHeader("Jwttoken"); - + // If not in header, try cookie if (jwtToken == null) { Cookie[] cookies = request.getCookies(); @@ -574,15 +577,15 @@ public String getLoginResponse(HttpServletRequest request) { } } } - + if (jwtToken == null) { logger.warn("Authentication failed: no token found in header or cookies."); throw new IEMRException("Authentication failed. Please log in again."); } - + // Extract user ID from the JWT token String userId = jwtUtil.getUserIdFromToken(jwtToken); - + // Get user details and prepare response User user = iemrAdminUserServiceImpl.getUserById(Long.parseLong(userId)); if (user == null) { @@ -759,7 +762,7 @@ public String saveUserSecurityQuesAns( } /** - * + * * @return security qtns */ @Operation(summary = "Get security quetions") @@ -908,7 +911,7 @@ public String userLogout(HttpServletRequest request) { } /** - * + * * @param key * @return */ @@ -952,7 +955,7 @@ public String forceLogout(@RequestBody ForceLogoutRequestModel request, HttpServ if (token == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); outputResponse.setError(new RuntimeException("No JWT token found in request")); - return outputResponse.toString(); + return outputResponse.toString(); } // Validate the token: Check if it is expired or in the deny list @@ -989,7 +992,7 @@ private String getJwtTokenFromCookies(HttpServletRequest request) { return null; } - + @Operation(summary = "User force log out") @RequestMapping(value = "/userForceLogout", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization") public String userForceLogout( @@ -1212,4 +1215,19 @@ private JSONObject prepareAuthenticationResponse(User mUser, String remoteAddres return iemrAdminUserServiceImpl.generateKeyAndValidateIP(responseObj, remoteAddress, remoteHost); } + @Operation(summary = "Get UserId based on userName") + @GetMapping(value = "/userName/{userName}", produces = MediaType.APPLICATION_JSON, headers = "Authorization") + public ResponseEntity getUserDetails(@PathVariable("userName") String userName) { + try { + List users = iemrAdminUserServiceImpl.getUserIdbyUserName(userName); + if (users.isEmpty()) { + return new ResponseEntity<>(Map.of("error", "UserName Not Found"), HttpStatus.NOT_FOUND); + } + User user = users.get(0); + return new ResponseEntity<>(Map.of("userName", user.getUserName(), "userId", user.getUserID()), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(Map.of("error", "Internal server error"), HttpStatus.INTERNAL_SERVER_ERROR); + } + + } } diff --git a/src/main/java/com/iemr/common/data/beneficiaryConsent/BeneficiaryConsentRequest.java b/src/main/java/com/iemr/common/data/beneficiaryConsent/BeneficiaryConsentRequest.java new file mode 100644 index 00000000..ac629a3c --- /dev/null +++ b/src/main/java/com/iemr/common/data/beneficiaryConsent/BeneficiaryConsentRequest.java @@ -0,0 +1,12 @@ +package com.iemr.common.data.beneficiaryConsent; + +import lombok.Data; + +@Data +public class BeneficiaryConsentRequest { + private String mobNo; + private int otp; + private String userName; + private String designation; + +} diff --git a/src/main/java/com/iemr/common/data/dynamic_from/FormDefinition.java b/src/main/java/com/iemr/common/data/dynamic_from/FormDefinition.java new file mode 100644 index 00000000..9e62b6d9 --- /dev/null +++ b/src/main/java/com/iemr/common/data/dynamic_from/FormDefinition.java @@ -0,0 +1,30 @@ +package com.iemr.common.data.dynamic_from; +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; +@Entity +@Data +@Table(name = "form_master") +public class FormDefinition { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "form_id") + private String formId; + + @Column(name = "form_name") + private String formName; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "module_id") + private FormModule module; + + @Column(name = "created_at") + private LocalDateTime createdAt = LocalDateTime.now(); + @Column(name = "version") + private Integer version; + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/data/dynamic_from/FormField.java b/src/main/java/com/iemr/common/data/dynamic_from/FormField.java new file mode 100644 index 00000000..39785ae9 --- /dev/null +++ b/src/main/java/com/iemr/common/data/dynamic_from/FormField.java @@ -0,0 +1,59 @@ +package com.iemr.common.data.dynamic_from; +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "form_fields") +public class FormField { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "form_id", referencedColumnName = "form_id") + private FormDefinition form; + + @Column(name = "section_title") + private String sectionTitle; + + @Column(name = "field_id") + private String fieldId; + + @Column(name = "label") + private String label; + + @Column(name = "type") + private String type; + + @Column(name = "is_required") + private Boolean isRequired; + + @Column(name = "is_visible") + private Boolean isVisible; + + @Column(name = "default_value") + private String defaultValue; + + @Column(name = "placeholder") + private String placeholder; + + @Column(name = "options", columnDefinition = "json") + private String options; + + @Column(name = "validation", columnDefinition = "json") + private String validation; // includes error messages now + + @Column(name = "conditional", columnDefinition = "json") + private String conditional; + + @Column(name = "sequence") + private Integer sequence; + + @Column(name = "created_at") + private LocalDateTime createdAt = LocalDateTime.now(); + +} diff --git a/src/main/java/com/iemr/common/data/dynamic_from/FormModule.java b/src/main/java/com/iemr/common/data/dynamic_from/FormModule.java new file mode 100644 index 00000000..ba5d0170 --- /dev/null +++ b/src/main/java/com/iemr/common/data/dynamic_from/FormModule.java @@ -0,0 +1,23 @@ +package com.iemr.common.data.dynamic_from; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "form_module") +public class FormModule { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "module_name") + private String moduleName; + + @Column(name = "created_at") + private LocalDateTime createdAt = LocalDateTime.now(); + +} diff --git a/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescribedMMUDrugDetail.java b/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescribedMMUDrugDetail.java new file mode 100644 index 00000000..e478420d --- /dev/null +++ b/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescribedMMUDrugDetail.java @@ -0,0 +1,119 @@ +package com.iemr.common.data.mmuDrugHistory; + +import java.sql.Date; +import java.sql.Timestamp; + +import com.google.gson.annotations.Expose; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import lombok.Data; + +@Entity +@Data +@Table(name = "t_prescribeddrug") +public class PrescribedMMUDrugDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Expose + @Column(name = "PrescribedDrugID") + private Long prescribedDrugID; + + @Expose + @Column(name = "BeneficiaryRegID") + private Long beneficiaryRegID; + + @Expose + @Column(name = "BenVisitID") + private Long benVisitID; + + @Expose + @Column(name = "ProviderServiceMapID") + private Integer providerServiceMapID; + + @Expose + @Column(name = "VisitCode") + private Long visitCode; + + @Expose + @Column(name = "PrescriptionID") + private Long prescriptionID; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "PrescriptionID", referencedColumnName = "PrescriptionID", insertable = false, updatable = false) + private PrescriptionMMU prescription; + + @Expose + @Column(name = "DrugForm") + private String formName; + + @Expose + @Column(name = "DrugTradeOrBrandName") + private String drugTradeOrBrandName; + + @Expose + @Column(name = "DrugID") + private Integer drugID; + + @Expose + @Column(name = "GenericDrugName") + private String drugName; + + @Expose + @Column(name = "DrugStrength") + private String drugStrength; + + @Expose + @Column(name = "Dose") + private String dose; + + @Expose + @Column(name = "Route") + private String route; + + @Expose + @Column(name = "Frequency") + private String frequency; + + @Expose + @Column(name = "Duration") + private String duration; + + @Expose + @Column(name = "DuartionUnit") + private String unit; + + @Expose + @Column(name = "RelationToFood") + private String relationToFood; + + @Expose + @Column(name = "SpecialInstruction") + private String instructions; + + @Expose + @Column(name = "QtyPrescribed") + private Integer qtyPrescribed; + + @Expose + @Column(name = "Deleted", insertable = false, updatable = true) + private Boolean deleted; + + @Expose + @Column(name = "Processed", insertable = false, updatable = true) + private String processed; + + @Expose + @Column(name = "CreatedBy") + private String createdBy; + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescriptionMMU.java b/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescriptionMMU.java new file mode 100644 index 00000000..cd3f655e --- /dev/null +++ b/src/main/java/com/iemr/common/data/mmuDrugHistory/PrescriptionMMU.java @@ -0,0 +1,43 @@ +package com.iemr.common.data.mmuDrugHistory; + +import com.google.gson.annotations.Expose; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Entity +@Data +@Table(name = "t_prescription") +public class PrescriptionMMU { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Expose + @Column(name = "PrescriptionID", insertable = false, updatable = false) + private Long prescriptionID; + + @Expose + @Column(name = "BenVisitID") + private Long benVisitID; + + @Expose + @Column(name = "ProviderServiceMapID") + private Integer providerServiceMapID; + + @Expose + @Column(name = "DiagnosisProvided") + private String diagnosisProvided; + + @Expose + @Column(name = "Remarks") + private String remarks; + + @Expose + @Column(name = "Deleted", insertable = false, updatable = true) + private Boolean deleted; + +} diff --git a/src/main/java/com/iemr/common/data/platform_feedback/Feedback.java b/src/main/java/com/iemr/common/data/platform_feedback/Feedback.java new file mode 100644 index 00000000..776d3c08 --- /dev/null +++ b/src/main/java/com/iemr/common/data/platform_feedback/Feedback.java @@ -0,0 +1,171 @@ +/* + * 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.platform_feedback; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "m_platform_feedback") +public class Feedback { + + @Id + @Column(name = "FeedbackID", length = 36, updatable = false, nullable = false) + private String feedbackId; + + @Column(name = "CreatedAt", nullable = false, insertable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "UpdatedAt", nullable = false, insertable = false, updatable = false) + private LocalDateTime updatedAt; + + @Min(1) + @Max(5) + @Column(name = "Rating", nullable = false) + private int rating; + + @Size(max = 2000) + @Column(name = "Comment", columnDefinition = "TEXT", nullable = true) + private String comment; + + @Column(name = "ServiceLine", nullable = false, length = 10) + private String serviceLine; + + @Column(name = "IsAnonymous", nullable = false) + private boolean isAnonymous = true; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "CategoryID", referencedColumnName = "CategoryID", nullable = false) + private FeedbackCategory category; + + @Column(name = "UserID", nullable = true) + private Integer userId; + + public Feedback() { + this.feedbackId = UUID.randomUUID().toString(); + } + + public Feedback(int rating, String comment, String serviceLine, boolean isAnonymous, FeedbackCategory category, Integer userId) { + this(); // ensures feedbackId + this.setRating(rating); + this.setComment(comment); + this.setServiceLine(serviceLine); + this.isAnonymous = isAnonymous; + this.category = category; + this.userId = userId; + } + + public String getFeedbackId() { + return feedbackId; + } + + // Don't usually set feedbackId externally, but keep setter for testing/migration if needed + public void setFeedbackId(String feedbackId) { + this.feedbackId = feedbackId; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public int getRating() { + return rating; + } + + public void setRating(int rating) { + if (rating < 1 || rating > 5) { + throw new IllegalArgumentException("Rating must be between 1 and 5."); + } + this.rating = rating; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + if (comment != null && comment.length() > 2000) { + throw new IllegalArgumentException("Comment cannot exceed 2000 characters"); + } + this.comment = (comment == null || comment.trim().isEmpty()) ? null : comment.trim(); + } + + public String getServiceLine() { + return serviceLine; + } + + public void setServiceLine(String serviceLine) { + if (serviceLine == null || serviceLine.trim().isEmpty()) { + throw new IllegalArgumentException("ServiceLine must not be null or empty."); + } + this.serviceLine = serviceLine; + } + + public boolean isAnonymous() { + return isAnonymous; + } + + public void setAnonymous(boolean anonymous) { + isAnonymous = anonymous; + if (anonymous) { + this.userId = null; + } + } + + public FeedbackCategory getCategory() { + return category; + } + + public void setCategory(FeedbackCategory category) { + if (category == null) { + throw new IllegalArgumentException("Category must not be null."); + } + this.category = category; + } + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + if (userId != null) { + this.isAnonymous = false; + } + } +} diff --git a/src/main/java/com/iemr/common/data/platform_feedback/FeedbackCategory.java b/src/main/java/com/iemr/common/data/platform_feedback/FeedbackCategory.java new file mode 100644 index 00000000..a3e4c692 --- /dev/null +++ b/src/main/java/com/iemr/common/data/platform_feedback/FeedbackCategory.java @@ -0,0 +1,153 @@ +/* + * 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.platform_feedback; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * FeedbackCategory maps to the m_feedback_category table. + */ +@Entity +@Table(name = "m_feedback_category", uniqueConstraints = { + @UniqueConstraint(name = "uq_category_slug", columnNames = "Slug") +}) +public class FeedbackCategory { + + @Id + @Column(name = "CategoryID", length = 36, updatable = false, nullable = false) + private String categoryId; + + @NotBlank + @Pattern(regexp = "^[a-z0-9]+(?:-[a-z0-9]+)*$", message = "Slug must be lowercase alphanumeric with optional single dashes") + @Size(max = 64) + @Column(name = "Slug", nullable = false, length = 64) + private String slug; + + @NotBlank + @Size(max = 128) + @Column(name = "Label", nullable = false, length = 128) + private String label; + + @NotBlank + @Size(max = 20) + @Column(name = "Scope", nullable = false, length = 20) + private String scope; + + @Column(name = "Active", nullable = false) + private boolean active = true; + + + @Column(name = "CreatedAt", nullable = false, insertable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "UpdatedAt", nullable = false, insertable = false, updatable = false) + private LocalDateTime updatedAt; + + // ===== Constructors ===== + public FeedbackCategory() { + // nothing — categoryId will be generated at persist if not provided + } + + public FeedbackCategory(String slug, String label, String scope, boolean active) { + this.slug = slug; + this.label = label; + this.scope = scope; + this.active = active; + } + + // ===== JPA lifecycle hooks ===== + /** + * Ensure CategoryID exists for new rows created via JPA. If DB seeding provides IDs, + * those will be used instead (we only set if null). + */ + @PrePersist + protected void ensureId() { + if (this.categoryId == null || this.categoryId.trim().isEmpty()) { + this.categoryId = UUID.randomUUID().toString(); + } + } + + // ===== Getters & Setters ===== + public String getCategoryId() { + return categoryId; + } + + // categoryId is generated at persist; setter kept for migration/tests + public void setCategoryId(String categoryId) { + this.categoryId = categoryId; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + // CreatedAt normally set by DB; setter available for tests/migrations + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + // UpdatedAt normally managed by DB + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/iemr/common/data/translation/Translation.java b/src/main/java/com/iemr/common/data/translation/Translation.java new file mode 100644 index 00000000..81a906fa --- /dev/null +++ b/src/main/java/com/iemr/common/data/translation/Translation.java @@ -0,0 +1,23 @@ +package com.iemr.common.data.translation; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "m_translation") +@Data +public class Translation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + @Column(name = "label_key") + private String labelKey; + @Column(name = "english") + private String english; + @Column(name = "hindi_translation") + private String hindiTranslation; + @Column(name = "is_active") + private Boolean isActive; +} diff --git a/src/main/java/com/iemr/common/data/userToken/UserTokenData.java b/src/main/java/com/iemr/common/data/userToken/UserTokenData.java new file mode 100644 index 00000000..0646972e --- /dev/null +++ b/src/main/java/com/iemr/common/data/userToken/UserTokenData.java @@ -0,0 +1,46 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.data.userToken; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +import java.sql.Timestamp; + +@Entity +@Table(name = "user_tokens", schema = "db_iemr") +@Data +public class UserTokenData { + @Id + @Column(name = "user_id") + Integer userId; + @Column(name = "token") + String token; + @Column(name = "updated_at") + Timestamp updatedAt; +} diff --git a/src/main/java/com/iemr/common/data/users/User.java b/src/main/java/com/iemr/common/data/users/User.java index e6184e60..275b0ec6 100644 --- a/src/main/java/com/iemr/common/data/users/User.java +++ b/src/main/java/com/iemr/common/data/users/User.java @@ -55,6 +55,7 @@ @Entity @Table(name = "m_user") @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -208,6 +209,10 @@ public class User implements Serializable { @Column(name = "failed_attempt") private Integer failedAttempt; + @Expose + @Column(name = "dhistoken") + private String dhistoken; + /* * protected User() { } */ @@ -217,7 +222,7 @@ public static User initializeUsers(Long userID, Integer titleID, String firstNam Timestamp dOJ, Integer qualificationID, String userName, String password, String emailID, Status m_Status, List m_UserServiceRoleMapping, String emergencyContactPerson, String emergencyContactNo, Boolean isSupervisor, Boolean deleted, String createdBy, Timestamp createdDate, - String modifiedBy, Timestamp lastModDate, String newPassword) { + String modifiedBy, Timestamp lastModDate, String newPassword, String dhistoken) { User user = new User(); user.userID = userID; user.titleID = titleID; @@ -244,6 +249,7 @@ public static User initializeUsers(Long userID, Integer titleID, String firstNam user.modifiedBy = modifiedBy; user.lastModDate = lastModDate; user.newPassword = newPassword; + user.dhistoken = dhistoken; return user; } @@ -530,6 +536,9 @@ public Integer getDesignationID() { public Designation getDesignation() { return designation; } + public String getDhistoken() { + return dhistoken; + } /* * public User(String userName, String password) { this.userName = userName; diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/FieldDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/FieldDTO.java new file mode 100644 index 00000000..894f03c2 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/FieldDTO.java @@ -0,0 +1,25 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class FieldDTO { + private Long id; + private String formId; + private String sectionTitle; + private String fieldId; + private String label; + private String type; + private Boolean isVisible; + private Boolean isRequired; + private String defaultValue; + private String placeholder; + private Integer sequence; + private String options; // ⬅️ changed from String to List + private String validation; // ⬅️ changed from String to Map + private String conditional; // ⬅️ changed from String to Map +} + diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/FieldResponseDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/FieldResponseDTO.java new file mode 100644 index 00000000..3415d91a --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/FieldResponseDTO.java @@ -0,0 +1,24 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class FieldResponseDTO { + private Long id; + private String formId; + private String sectionTitle; + private String fieldId; + private String label; + private Boolean visible; + private String type; + private Boolean isRequired; + private String defaultValue; + private String placeholder; + private Integer sequence; + private List options; + private Map validation; + private Map conditional; +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/FormDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/FormDTO.java new file mode 100644 index 00000000..24788cac --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/FormDTO.java @@ -0,0 +1,10 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +@Data +public class FormDTO { + private String formId; + private String formName; + private Long moduleId; +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/FormResponseDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/FormResponseDTO.java new file mode 100644 index 00000000..26563927 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/FormResponseDTO.java @@ -0,0 +1,13 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +import java.util.List; + +@Data +public class FormResponseDTO { + private Integer version; + private String formId; + private String formName; + private List sections; +} diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/GroupedFieldResponseDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/GroupedFieldResponseDTO.java new file mode 100644 index 00000000..840c5097 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/GroupedFieldResponseDTO.java @@ -0,0 +1,11 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +import java.util.List; + +@Data +public class GroupedFieldResponseDTO { + private String sectionTitle; + private List fields; +} diff --git a/src/main/java/com/iemr/common/dto/dynamicForm/ModuleDTO.java b/src/main/java/com/iemr/common/dto/dynamicForm/ModuleDTO.java new file mode 100644 index 00000000..3f4240c4 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/dynamicForm/ModuleDTO.java @@ -0,0 +1,8 @@ +package com.iemr.common.dto.dynamicForm; + +import lombok.Data; + +@Data +public class ModuleDTO { + private String moduleName; +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/identity/CommonIdentityDTO.java b/src/main/java/com/iemr/common/dto/identity/CommonIdentityDTO.java index 431a267a..8e364eb3 100644 --- a/src/main/java/com/iemr/common/dto/identity/CommonIdentityDTO.java +++ b/src/main/java/com/iemr/common/dto/identity/CommonIdentityDTO.java @@ -54,6 +54,8 @@ public class CommonIdentityDTO { private Integer beneficiaryRegId; private Integer communityId; private String community; + private Boolean isConsent=false; + private Timestamp dob; private Integer ageAtMarriage; private Integer educationId; diff --git a/src/main/java/com/iemr/common/dto/platform_feedback/CategoryResponse.java b/src/main/java/com/iemr/common/dto/platform_feedback/CategoryResponse.java new file mode 100644 index 00000000..c01ff197 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/platform_feedback/CategoryResponse.java @@ -0,0 +1,24 @@ +/* + * 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.dto.platform_feedback; + +public record CategoryResponse(String categoryId, String slug, String label, String scope, boolean active) {} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackRequest.java b/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackRequest.java new file mode 100644 index 00000000..02c52c3a --- /dev/null +++ b/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackRequest.java @@ -0,0 +1,37 @@ +/* + * 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.dto.platform_feedback; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public record FeedbackRequest( + @Min(1) @Max(5) int rating, + String categoryId, + String categorySlug, + @Size(max = 2000) String comment, + boolean isAnonymous, + @Pattern(regexp = "^(1097|104|AAM|MMU|TM|ECD)$") String serviceLine, + Integer userId +) {} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackResponse.java b/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackResponse.java new file mode 100644 index 00000000..51003353 --- /dev/null +++ b/src/main/java/com/iemr/common/dto/platform_feedback/FeedbackResponse.java @@ -0,0 +1,26 @@ +/* + * 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.dto.platform_feedback; +import java.time.LocalDateTime; + + +public record FeedbackResponse(String id, LocalDateTime createdAt) {} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/dto/sms/SMSTemplateDTO.java b/src/main/java/com/iemr/common/dto/sms/SMSTemplateDTO.java new file mode 100644 index 00000000..ab71d8cc --- /dev/null +++ b/src/main/java/com/iemr/common/dto/sms/SMSTemplateDTO.java @@ -0,0 +1,114 @@ +package com.iemr.common.dto.sms; + +public class SMSTemplateDTO { + private Integer smsTemplateID; + private String smsTemplateName; + private String smsTemplate; + private String dltTemplateId; + private String smsSenderID; + private Integer smsTypeID; + private Integer providerServiceMapID; + private Boolean deleted; + private String createdBy; + private String modifiedBy; + private String createdDate; + private String lastModDate; + + // Getters and Setters for all fields + + public Integer getSmsTemplateID() { + return smsTemplateID; + } + + public void setSmsTemplateID(Integer smsTemplateID) { + this.smsTemplateID = smsTemplateID; + } + + public String getSmsTemplateName() { + return smsTemplateName; + } + + public void setSmsTemplateName(String smsTemplateName) { + this.smsTemplateName = smsTemplateName; + } + + public String getSmsTemplate() { + return smsTemplate; + } + + public void setSmsTemplate(String smsTemplate) { + this.smsTemplate = smsTemplate; + } + + public String getDltTemplateId() { + return dltTemplateId; + } + + public void setDltTemplateId(String dltTemplateId) { + this.dltTemplateId = dltTemplateId; + } + + public String getSmsSenderID() { + return smsSenderID; + } + + public void setSmsSenderID(String smsSenderID) { + this.smsSenderID = smsSenderID; + } + + public Integer getSmsTypeID() { + return smsTypeID; + } + + public void setSmsTypeID(Integer smsTypeID) { + this.smsTypeID = smsTypeID; + } + + public Integer getProviderServiceMapID() { + return providerServiceMapID; + } + + public void setProviderServiceMapID(Integer providerServiceMapID) { + this.providerServiceMapID = providerServiceMapID; + } + + public Boolean getDeleted() { + return deleted; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public String getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public String getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(String createdDate) { + this.createdDate = createdDate; + } + + public String getLastModDate() { + return lastModDate; + } + + public void setLastModDate(String lastModDate) { + this.lastModDate = lastModDate; + } +} diff --git a/src/main/java/com/iemr/common/exception/BadRequestException.java b/src/main/java/com/iemr/common/exception/BadRequestException.java new file mode 100644 index 00000000..65d146a6 --- /dev/null +++ b/src/main/java/com/iemr/common/exception/BadRequestException.java @@ -0,0 +1,31 @@ +/* + * 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.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { super(message); } + public BadRequestException(String message, Throwable cause) { super(message, cause); } +} diff --git a/src/main/java/com/iemr/common/exception/ValidationExceptionHandler.java b/src/main/java/com/iemr/common/exception/ValidationExceptionHandler.java new file mode 100644 index 00000000..750d221c --- /dev/null +++ b/src/main/java/com/iemr/common/exception/ValidationExceptionHandler.java @@ -0,0 +1,54 @@ +package com.iemr.common.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class ValidationExceptionHandler { + + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationErrors(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + + ex.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + + logger.error("Validation failed: {}", errors); + + Map response = new HashMap<>(); + response.put("status", "ERROR"); + response.put("statusCode", 5000); + response.put("errorMessage", "Input validation failed"); + response.put("errors", errors); + + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgument(IllegalArgumentException ex) { + logger.error("Illegal argument: {}", ex.getMessage()); + + Map response = new HashMap<>(); + response.put("status", "ERROR"); + response.put("statusCode", 5000); + response.put("errorMessage", ex.getMessage()); + + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } +} + + diff --git a/src/main/java/com/iemr/common/filter/PlatformFeedbackRateLimitFilter.java b/src/main/java/com/iemr/common/filter/PlatformFeedbackRateLimitFilter.java new file mode 100644 index 00000000..d6dddf55 --- /dev/null +++ b/src/main/java/com/iemr/common/filter/PlatformFeedbackRateLimitFilter.java @@ -0,0 +1,216 @@ +/* + * 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.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.Duration; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Base64; +import java.util.concurrent.TimeUnit; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + + +@Component +@ConditionalOnProperty(prefix = "platform.feedback.ratelimit", name = "enabled", havingValue = "true", matchIfMissing = false) +@Order(Ordered.HIGHEST_PRECEDENCE + 10) // run early (adjust order as needed) +public class PlatformFeedbackRateLimitFilter extends OncePerRequestFilter { + + private final StringRedisTemplate redis; + private final String pepper; + private final boolean trustForwardedFor; + private final String forwardedForHeader; + + // Limits & TTLs (tweak if needed) + private static final int MINUTE_LIMIT = 10; + private static final int DAY_LIMIT = 100; + private static final int USER_DAY_LIMIT = 50; // for identified users + private static final Duration MINUTE_WINDOW = Duration.ofMinutes(1); + private static final Duration DAY_WINDOW = Duration.ofHours(48); // keep key TTL ~48h + private static final Duration FAIL_COUNT_WINDOW = Duration.ofMinutes(5); + private static final int FAILS_TO_BACKOFF = 3; + private static final Duration BACKOFF_WINDOW = Duration.ofMinutes(15); + + public PlatformFeedbackRateLimitFilter(StringRedisTemplate redis, + org.springframework.core.env.Environment env) { + this.redis = redis; + this.pepper = env.getProperty("platform.feedback.pepper", ""); + this.trustForwardedFor = Boolean.parseBoolean(env.getProperty("platform.feedback.trust-forwarded-for", "true")); + this.forwardedForHeader = env.getProperty("platform.feedback.forwarded-for-header", "X-Forwarded-For"); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + // Only filter specific endpoints (POST to platform-feedback). Keep it narrow. + String path = request.getRequestURI(); + String method = request.getMethod(); + // adjust path as needed (supports /common-api/platform-feedback and subpaths) + return !("POST".equalsIgnoreCase(method) && path != null && path.matches("^/platform-feedback(?:/.*)?$")); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + // compute day key + String clientIp = extractClientIp(request); + if (clientIp == null || clientIp.isBlank()) { + // If we can't identify an IP, be conservative and allow but log (or optionally block) + filterChain.doFilter(request, response); + return; + } + + String today = LocalDate.now(ZoneId.of("Asia/Kolkata")).toString().replaceAll("-", ""); // yyyyMMdd + String ipHash = sha256Base64(clientIp + pepper + today); // base64 shorter storage; not reversible without pepper + String minKey = "rl:fb:min:" + ipHash + ":" + (System.currentTimeMillis() / 60000L); // minute-slotted + String dayKey = "rl:fb:day:" + today + ":" + ipHash; + String failKey = "rl:fb:fail:" + ipHash; + String backoffKey = "rl:fb:backoff:" + ipHash; + + // If under backoff -> respond 429 with Retry-After = TTL + Long backoffTtl = getTtlSeconds(backoffKey); + if (backoffTtl != null && backoffTtl > 0) { + sendTooMany(response, backoffTtl); + return; + } + + // Minute window check (INCR + TTL if first) + long minuteCount = incrementWithExpire(minKey, 1, MINUTE_WINDOW.getSeconds()); + if (minuteCount > MINUTE_LIMIT) { + handleFailureAndMaybeBackoff(failKey, backoffKey, response, minKey, dayKey); + return; + } + + // Day window check + long dayCount = incrementWithExpire(dayKey, 1, DAY_WINDOW.getSeconds()); + if (dayCount > DAY_LIMIT) { + handleFailureAndMaybeBackoff(failKey, backoffKey, response, minKey, dayKey); + return; + } + + // Optional: per-user daily cap if we can extract an authenticated user id from header/jwt + Integer userId = extractUserIdFromRequest(request); // implement extraction as per your JWT scheme + if (userId != null) { + String userDayKey = "rl:fb:user:" + today + ":" + userId; + long ucount = incrementWithExpire(userDayKey, 1, DAY_WINDOW.getSeconds()); + if (ucount > USER_DAY_LIMIT) { + handleFailureAndMaybeBackoff(failKey, backoffKey, response, minKey, userDayKey); + return; + } + } + + // All checks passed — proceed to controller + filterChain.doFilter(request, response); + } + + // increments key by delta; sets TTL when key is new (INCR returns 1) + private long incrementWithExpire(String key, long delta, long ttlSeconds) { + Long value = redis.opsForValue().increment(key, delta); + if (value != null && value == 1L) { + redis.expire(key, ttlSeconds, TimeUnit.SECONDS); + } + return value == null ? 0L : value; + } + + private void handleFailureAndMaybeBackoff(String failKey, String backoffKey, HttpServletResponse response, String trigKey, String dayKey) throws IOException { + // increment fail counter and possibly set backoff + Long fails = redis.opsForValue().increment(failKey, 1); + if (fails != null && fails == 1L) { + redis.expire(failKey, FAIL_COUNT_WINDOW.getSeconds(), TimeUnit.SECONDS); + } + if (fails != null && fails >= FAILS_TO_BACKOFF) { + // set backoff flag + redis.opsForValue().set(backoffKey, "1", BACKOFF_WINDOW.getSeconds(), TimeUnit.SECONDS); + sendTooMany(response, BACKOFF_WINDOW.getSeconds()); + return; + } + + // otherwise respond with Retry-After for the triggering key TTL (minute/day) + Long retryAfter = getTtlSeconds(trigKey); + if (retryAfter == null || retryAfter <= 0) retryAfter = 60L; + sendTooMany(response, retryAfter); + } + + private void sendTooMany(HttpServletResponse response, long retryAfterSeconds) throws IOException { + response.setStatus(429); + response.setHeader("Retry-After", String.valueOf(retryAfterSeconds)); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + String body = String.format("{\"code\":\"RATE_LIMITED\",\"message\":\"Too many requests\",\"retryAfter\":%d}", retryAfterSeconds); + response.getWriter().write(body); + } + + private Long getTtlSeconds(String key) { + Long ttl = redis.getExpire(key, TimeUnit.SECONDS); + return ttl == null || ttl < 0 ? null : ttl; + } + + private String extractClientIp(HttpServletRequest request) { + if (trustForwardedFor) { + String header = request.getHeader(forwardedForHeader); + if (StringUtils.hasText(header)) { + // X-Forwarded-For may contain comma-separated list; take the first (client) entry + String[] parts = header.split(","); + if (parts.length > 0) { + String ip = parts[0].trim(); + if (StringUtils.hasText(ip)) return ip; + } + } + } + return request.getRemoteAddr(); + } + + private Integer extractUserIdFromRequest(HttpServletRequest request) { + // implement based on how you propagate JWT or user info. + // Example: if your gateway injects header X-User-Id for authenticated requests: + String s = request.getHeader("X-User-Id"); + if (StringUtils.hasText(s)) { + try { return Integer.valueOf(s); } catch (NumberFormatException ignored) {} + } + // If JWT parsing required, do it here, but keep this filter light — prefer upstream auth filter to populate a header. + return null; + } + + private static String sha256Base64(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashed = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + // base64 url-safe or normal base64 — either is fine; base64 is shorter than hex + return Base64.getUrlEncoder().withoutPadding().encodeToString(hashed); + } catch (Exception ex) { + throw new RuntimeException("sha256 failure", ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java b/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java index 5e8034af..1480f306 100644 --- a/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java +++ b/src/main/java/com/iemr/common/mapper/CommonIdentityMapperDecorator.java @@ -168,8 +168,17 @@ public CommonIdentityDTO beneficiaryModelCommonIdentityDTO(BeneficiaryModel bene Short maritalStatusID = beneficiary.getMaritalStatusID(); if (maritalStatusID != null) { commonIdentityDTO.setMaritalStatusId(maritalStatusID.intValue()); - commonIdentityDTO.setMaritalStatus( - maritalStatusMapper.maritalStatusByIDToResponse(maritalStatusID.intValue()).getStatus()); + var model = maritalStatusMapper.maritalStatusByIDToResponse(maritalStatusID.intValue()); + if (model != null) { + commonIdentityDTO.setMaritalStatus(model.getStatus()); + } else { + // Option 1: Return null + commonIdentityDTO.setMaritalStatus(null); + // Option 2: Or set a default + // commonIdentityDTO.setMaritalStatus("Unknown"); + // Option 3: Or log a warning + // logger.warn("Invalid maritalStatusID: {}", maritalStatusID); + } } commonIdentityDTO.setMaritalStatus(beneficiary.getMaritalStatusName()); commonIdentityDTO.setGender(beneficiary.getGenderName()); diff --git a/src/main/java/com/iemr/common/mapper/sms/SMSMapper.java b/src/main/java/com/iemr/common/mapper/sms/SMSMapper.java index 7fe629f1..1982a74f 100644 --- a/src/main/java/com/iemr/common/mapper/sms/SMSMapper.java +++ b/src/main/java/com/iemr/common/mapper/sms/SMSMapper.java @@ -21,7 +21,9 @@ */ package com.iemr.common.mapper.sms; +import java.text.SimpleDateFormat; import java.util.List; +import java.sql.Timestamp; import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; @@ -33,6 +35,7 @@ import com.iemr.common.data.sms.SMSParametersMap; import com.iemr.common.data.sms.SMSTemplate; import com.iemr.common.data.sms.SMSType; +import com.iemr.common.dto.sms.SMSTemplateDTO; import com.iemr.common.model.sms.CreateSMSRequest; import com.iemr.common.model.sms.FullSMSTemplateResponse; import com.iemr.common.model.sms.SMSParameterMapModel; @@ -110,4 +113,25 @@ public interface SMSMapper @IterableMapping(elementTargetType = FullSMSTemplateResponse.class) List smsTemplateToFullResponse(List smsTemplate); + @Mapping(source = "smsTemplateID", target = "smsTemplateID") + @Mapping(source = "smsTemplateName", target = "smsTemplateName") + @Mapping(source = "smsTemplate", target = "smsTemplate") + @Mapping(source = "dltTemplateId", target = "dltTemplateId") + @Mapping(source = "smsSenderID", target = "smsSenderID") + @Mapping(source = "smsTypeID", target = "smsTypeID") + @Mapping(source = "providerServiceMapID", target = "providerServiceMapID") + @Mapping(source = "deleted", target = "deleted") + @Mapping(source = "createdBy", target = "createdBy") + @Mapping(source = "modifiedBy", target = "modifiedBy") + @Mapping(target = "createdDate", expression = "java(formatDate(template.getCreatedDate()))") + @Mapping(target = "lastModDate", expression = "java(formatDate(template.getLastModDate()))") + SMSTemplateDTO smsTemplateToDTO(SMSTemplate template); + + default String formatDate(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + return sdf.format(timestamp); + } } diff --git a/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryModel.java b/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryModel.java index dfb76d00..e7a7a3de 100644 --- a/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryModel.java +++ b/src/main/java/com/iemr/common/model/beneficiary/BeneficiaryModel.java @@ -78,6 +78,51 @@ public class BeneficiaryModel implements Comparable { // private List outboundCallRequests; // private List beneficiaryCalls; // private List feedbacks; + @Expose + private Boolean isConsent=false; + + @Expose + private Boolean isDeath; + + @Expose + private String dateOfDeath; + + @Expose + private String isDeathValue; + + @Expose + private String timeOfDeath; + + @Expose + private String reasonOfDeath; + + @Expose + private Integer reasonOfDeathId; + + @Expose + private String placeOfDeath; + + @Expose + private Integer placeOfDeathId; + + @Expose + private String otherPlaceOfDeath; + + @Expose + private Boolean isSpouseAdded; + + @Expose + private Boolean isChildrenAdded; + + @Expose + private Boolean isMarried; + + @Expose + private Integer doYouHavechildren; + + @Expose + private Integer noofAlivechildren; + @Expose private String beneficiaryID; @Expose diff --git a/src/main/java/com/iemr/common/model/beneficiary/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/model/beneficiary/RMNCHBeneficiaryDetailsRmnch.java new file mode 100644 index 00000000..b6bc8512 --- /dev/null +++ b/src/main/java/com/iemr/common/model/beneficiary/RMNCHBeneficiaryDetailsRmnch.java @@ -0,0 +1,502 @@ +package com.iemr.common.model.beneficiary; + +import com.google.gson.annotations.Expose; +import lombok.Data; + +import jakarta.persistence.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; + +@Entity +@Table(name = "i_beneficiarydetails_rmnch",schema = "db_identity") +@Data +public class RMNCHBeneficiaryDetailsRmnch { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Expose + @Column(name = "beneficiaryDetails_RmnchId", insertable = false, updatable = false) + private Long beneficiaryDetails_RmnchId; + + @Expose + @Column(name = "BeneficiaryRegID") + private Long BenRegId; + + @Expose + @Column(name = "aadhaNo") + private String aadhaNo; + + @Expose + @Column(name = "aadha_no") + private String aadha_no; + + @Expose + @Column(name = "aadha_noId") + private Integer aadha_noId; + + @Expose + @Column(name = "age") + private Integer age; + + @Expose + @Column(name = "ageAtMarriage") + private Integer ageAtMarriage; + + @Expose + @Column(name = "age_unit") + private String age_unit; + + @Expose + @Column(name = "age_unitId") + private Integer age_unitId; + + @Expose + @Column(name = "childRegisteredAWCID") + private Long childRegisteredAWCID; + + @Expose + @Column(name = "childRegisteredSchoolID") + private Integer childRegisteredSchoolID; + + @Expose + @Column(name = "dateofdelivey") + private Timestamp dateofdelivey; + + @Expose + @Column(name = "expectedDateofDelivery") + private Timestamp expectedDateOfDelivery; + + @Expose + @Column(name = "facilitySectionID") + private Integer facilitySectionID; + + @Expose + @Column(name = "guidelineId") + private String guidelineId; + + @Expose + @Column(name = "houseoldId") + private Long houseoldId; + + @Expose + @Column(name = "lastDeliveryConductedID") + private Integer lastDeliveryConductedID; + + @Expose + @Column(name = "lastMenstrualPeriod") + private String lastMenstrualPeriod; + + @Expose + @Column(name = "latitude") + private BigDecimal latitude; + + @Expose + @Column(name = "lengthofMenstrualCycleId") + private Integer lengthofMenstrualCycleId; + + @Expose + @Column(name = "literacyId") + private Integer literacyId; + + @Expose + @Column(name = "longitude") + private BigDecimal longitude; + + @Expose + @Column(name = "menstrualBFDId") + private Integer menstrualBFDId; + + @Expose + @Column(name = "menstrualProblemId") + private Integer menstrualProblemId; + + @Expose + @Column(name = "menstrualStatusId") + private Integer menstrualStatusId; + + @Expose + @Column(name = "mobileOthers") + private String mobileOthers; + + @Expose + @Column(name = "mobilenoofRelation") + private String mobilenoofRelation; + + @Expose + @Column(name = "mobilenoofRelationId") + private Integer mobilenoofRelationId; + + @Expose + @Column(name = "motherName") + private String motherName; + + @Expose + @Column(name = "ncd_priority") + private Integer ncd_priority; + + @Expose + @Column(name = "need_opcareId") + private Integer need_opcareId; + + @Expose + @Column(name = "previousLiveBirth") + private String previousLiveBirth; + + @Expose + @Column(name = "rchid") + private String rchid; + + @Expose + @Column(name = "registrationDate") + private Timestamp registrationDate; + + @Expose + @Column(name = "registrationType") + private String registrationType; + + @Expose + @Column(name = "regularityofMenstrualCycleId") + private Integer regularityofMenstrualCycleId; + + @Expose + @Column(name = "religionOthers") + private String religionOthers; + + @Expose + @Column(name = "reproductiveStatus") + private String reproductiveStatus; + + @Expose + @Column(name = "reproductiveStatusId") + private Integer reproductiveStatusId; + + @Expose + @Column(name = "serverUpdatedStatus") + private Integer serverUpdatedStatus; + + @Expose + @Column(name = "typeofSchoolID") + private Integer typeofSchoolID; + + @Expose + @Column(name = "whoConductedDeliveryID") + private Integer whoConductedDeliveryID; + + @Expose + @Column(name = "Deleted") + private Boolean deleted; + + @Expose + @Column(name = "Processed") + private String Processed = "N"; + + @Expose + @Column(name = "CreatedBy") + private String createdBy; + + @Expose + @Column(name = "CreatedDate") + private Timestamp createdDate; + + @Expose + @Column(name = "Reserved") + private Boolean reserved; + + @Expose + @Column(name = "ReservedFor") + private String reservedFor; + + @Expose + @Column(name = "ReservedOn") + private String reservedOn; + + @Expose + @Column(name = "ReservedById") + private Integer reservedById; + + @Expose + @Column(name = "ModifiedBy") + private String updatedBy; + + @Expose + @Column(name = "LastModDate") + private Timestamp updatedDate; + + @Expose + @Column(name = "VanSerialNo") + private Long id; + + @Expose + @Column(name = "VanID") + private Integer VanID; + + @Expose + @Column(name = "VehicalNo") + private String vehicalNo; + + @Expose + @Column(name = "ParkingPlaceID") + private Integer parkingPlaceID; + + @Expose + @Column(name = "SyncedBy") + private String syncedBy; + + @Expose + @Column(name = "SyncedDate") + private Timestamp syncedDate; + + @Expose + @Column(name = "ProviderServiceMapID") + private Integer ProviderServiceMapID; + + @Expose + @Column(name = "deviceId") + private Integer deviceId; + + @Expose + @Column(name = "beneficiaryId") + private Long benficieryid; + + // new fields 30-06-2021 + @Expose + @Column(name = "RelatedBeneficiaryIds") + private String relatedBeneficiaryIdsDB; + @Expose + @Column(name = "HRPStatus") + private Boolean hrpStatus; + @Expose + @Column(name = "ImmunizationStatus") + private Boolean immunizationStatus; + @Expose + @Column(name = "NishchayPregnancyStatus") + private String nishchayPregnancyStatus; + @Expose + @Column(name = "NishchayPregnancyStatusPosition") + private Integer nishchayPregnancyStatusPosition; + @Expose + @Column(name = "NishchayDeliveryStatus") + private String nishchayDeliveryStatus; + @Expose + @Column(name = "NishchayDeliveryStatusPosition") + private Integer nishchayDeliveryStatusPosition; + + @Expose + @Column(name = "FamilyHeadRelation") + private String familyHeadRelation; + @Expose + @Column(name = "FamilyHeadRelationPosition") + private Integer familyHeadRelationPosition; + + @Expose + @Column(name = "MenstrualStatus") + private String menstrualStatus; + @Expose + @Column(name = "ComplicationsOthers") + private String complicationsOthers; + + @Expose + @Transient + private Long[] relatedBeneficiaryIds; + + @Expose + @Transient + private String literacyStatus; + + @Expose + @Transient + private String branchName; + + // location + @Expose + @Transient + private Integer countryId; + @Expose + @Transient + private String countryName; + @Expose + @Transient + private Integer stateId; + @Expose + @Transient + private String stateName; +// @Expose +// @Transient +// private Integer districtId; +// @Expose +// @Transient +// private String districtName; + @Expose + @Transient + private Integer blockId; + @Expose + @Transient + private String blockName; + @Expose + @Transient + private Integer villageId; + @Expose + @Transient + private String villageName; + + @Expose + @Transient + private Integer servicePointID; + @Expose + @Transient + private String servicePointName; + + @Expose + @Transient + private Integer zoneID; + @Expose + @Transient + private String zoneName; + + @Expose + @Transient + private String addressLine1; + @Expose + @Transient + private String addressLine2; + @Expose + @Transient + private String addressLine3; + + // ---------------------------------------------- + + // Extra data from platform + @Expose + @Transient + private String bankAccount; + @Expose + @Transient + private String community; + @Expose + @Transient + private Integer communityId; + @Expose + @Transient + private String contact_number; + @Expose + @Transient + private Integer currSubDistrictId; + @Expose + @Transient + private Integer districtid; + @Expose + @Transient + private String districtname; + @Expose + @Transient + private Timestamp dob; + @Expose + @Transient + private String fatherName; + @Expose + @Transient + private String firstName; + @Expose + @Transient + private String gender; + @Expose + @Transient + private Integer genderId; + @Expose + @Transient + private String ifscCode; + @Expose + @Transient + private String lastName; + @Expose + @Transient + private String maritalstatus; + @Expose + @Transient + private Integer maritalstatusId; + @Expose + @Transient + private Timestamp marriageDate; + @Expose + @Transient + private String nameOfBank; + @Expose + @Transient + private String religion; + @Expose + @Transient + private BigInteger religionID; + @Expose + @Transient + private String spousename; + @Expose + @Transient + private String user_image; + + // 08102020 + @Expose + @Transient + private String ageFull; + + @Expose + private Boolean isDeath; + + @Expose + private String isDeathValue; + + @Expose + private String dateOfDeath; + + + @Expose + private String timeOfDeath; + + @Expose + private String reasonOfDeath; + + @Expose + private Integer reasonOfDeathId; + + @Expose + private String placeOfDeath; + + @Expose + private Integer placeOfDeathId; + + @Expose + private String otherPlaceOfDeath; + + // 07-07-2021 + @Expose + @Column(name = "WhoConductedDelivery") + private String whoConductedDelivery; + @Expose + @Column(name = "LastDeliveryConducted") + private String lastDeliveryConducted; + @Expose + @Column(name = "FacilitySelection") + private String facilitySelection; + @Expose + @Column(name = "DeliveryDate") + private String deliveryDate; + @Expose + @Column(name = "ChildRegisteredSchool") + private String childRegisteredSchool; + @Expose + @Column(name = "TypeOfSchool") + private String typeOfSchool; +// 13-04-2022 + @Expose + @Column(name = "dateMarriage") + private Timestamp dateMarriage; + @Expose + @Column(name = "nayiPahalDeliveryStatus") + private String nayiPahalDeliveryStatus; + @Expose + @Column(name = "nayiPahalDeliveryStatusPosition") + private Integer nayiPahalDeliveryStatusPosition; + @Expose + @Column(name = "noOfDaysForDelivery") + private Integer noOfDaysForDelivery; + +} diff --git a/src/main/java/com/iemr/common/model/notification/NotificationMessage.java b/src/main/java/com/iemr/common/model/notification/NotificationMessage.java new file mode 100644 index 00000000..58343034 --- /dev/null +++ b/src/main/java/com/iemr/common/model/notification/NotificationMessage.java @@ -0,0 +1,38 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.model.notification; + +import lombok.Data; + +import java.util.Map; + +@Data +public class NotificationMessage { + private String appType; + private String token; + private String title; + private String body; + private Map data; +} diff --git a/src/main/java/com/iemr/common/model/notification/UserToken.java b/src/main/java/com/iemr/common/model/notification/UserToken.java new file mode 100644 index 00000000..3ea36588 --- /dev/null +++ b/src/main/java/com/iemr/common/model/notification/UserToken.java @@ -0,0 +1,33 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.model.notification; + +import lombok.Data; + +@Data +public class UserToken { + Integer userId; + String token; +} diff --git a/src/main/java/com/iemr/common/model/sms/CreateSMSRequest.java b/src/main/java/com/iemr/common/model/sms/CreateSMSRequest.java index 358730c9..774a8517 100644 --- a/src/main/java/com/iemr/common/model/sms/CreateSMSRequest.java +++ b/src/main/java/com/iemr/common/model/sms/CreateSMSRequest.java @@ -23,17 +23,32 @@ import java.util.List; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import lombok.Data; @Data public class CreateSMSRequest { Integer smsTemplateID; + @NotBlank(message = "SMS template name is required") + @Size(min = 3, max = 100, message = "Template name must be between 3 and 100 characters") + @Pattern(regexp = "^[a-zA-Z0-9_\\s-]+$", + message = "Template name can only contain alphanumeric characters, spaces, hyphens and underscores") String smsTemplateName; + @NotBlank(message = "SMS template content is required") + @Size(min = 10, max = 500, message = "Template content must be between 10 and 500 characters") + @Pattern(regexp = "^[^<>]*$", + message = "Template cannot contain < or > characters") String smsTemplate; Integer smsTypeID; SMSTypeModel smsType; + @NotNull(message = "Provider service map ID is required") + @Positive(message = "Provider service map ID must be positive") Integer providerServiceMapID; String createdBy; + @Valid + @NotEmpty(message = "At least one SMS parameter is required") + @Size(max = 20, message = "Maximum 20 parameters allowed") List smsParameterMaps; } diff --git a/src/main/java/com/iemr/common/model/sms/SMSParameterMapModel.java b/src/main/java/com/iemr/common/model/sms/SMSParameterMapModel.java index 032e90d2..fb9133e7 100644 --- a/src/main/java/com/iemr/common/model/sms/SMSParameterMapModel.java +++ b/src/main/java/com/iemr/common/model/sms/SMSParameterMapModel.java @@ -21,6 +21,7 @@ */ package com.iemr.common.model.sms; +import jakarta.validation.constraints.*; import lombok.Data; @Data @@ -30,7 +31,14 @@ public class SMSParameterMapModel Integer smsTemplateID; String createdBy; String modifiedBy; + @Size(max = 200, message = "Parameter value must not exceed 200 characters") + @Pattern(regexp = "^[^<>\"';&|`$(){}\\[\\]]*$", + message = "Parameter value contains invalid characters") String smsParameterValue; + @NotBlank(message = "Parameter name is required") + @Size(min = 2, max = 50, message = "Parameter name must be between 2 and 50 characters") + @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", + message = "Parameter name must start with a letter and contain only alphanumeric and underscore") String smsParameterName; String smsParameterType; } diff --git a/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java b/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java new file mode 100644 index 00000000..817f26f0 --- /dev/null +++ b/src/main/java/com/iemr/common/repo/userToken/UserTokenRepo.java @@ -0,0 +1,31 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.repo.userToken; + +import com.iemr.common.data.userToken.UserTokenData; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserTokenRepo extends JpaRepository { +} diff --git a/src/main/java/com/iemr/common/repository/beneficiary/BeneficiaryDetailsRmnchRepository.java b/src/main/java/com/iemr/common/repository/beneficiary/BeneficiaryDetailsRmnchRepository.java new file mode 100644 index 00000000..896d696a --- /dev/null +++ b/src/main/java/com/iemr/common/repository/beneficiary/BeneficiaryDetailsRmnchRepository.java @@ -0,0 +1,10 @@ +//package com.iemr.common.repository.beneficiary; +// +//import com.iemr.common.model.beneficiary.RMNCHBeneficiaryDetailsRmnch; +//import org.springframework.data.jpa.repository.JpaRepository; +//import org.springframework.stereotype.Repository; +// +//@Repository +//public interface BeneficiaryDetailsRmnchRepository extends JpaRepository { +// +//} diff --git a/src/main/java/com/iemr/common/repository/dynamic_form/FieldRepository.java b/src/main/java/com/iemr/common/repository/dynamic_form/FieldRepository.java new file mode 100644 index 00000000..4aea5698 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/dynamic_form/FieldRepository.java @@ -0,0 +1,12 @@ +package com.iemr.common.repository.dynamic_form; + +import com.iemr.common.data.dynamic_from.FormField; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface FieldRepository extends JpaRepository { + List findByForm_FormIdOrderBySequenceAsc(String formId); +} diff --git a/src/main/java/com/iemr/common/repository/dynamic_form/FormRepository.java b/src/main/java/com/iemr/common/repository/dynamic_form/FormRepository.java new file mode 100644 index 00000000..50331dd2 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/dynamic_form/FormRepository.java @@ -0,0 +1,14 @@ +package com.iemr.common.repository.dynamic_form; + +import com.iemr.common.data.dynamic_from.FormDefinition; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface FormRepository extends JpaRepository { + Optional findByFormId(String formId); + List findByModule_Id(Long moduleId); +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/repository/dynamic_form/ModuleRepository.java b/src/main/java/com/iemr/common/repository/dynamic_form/ModuleRepository.java new file mode 100644 index 00000000..bafbfbea --- /dev/null +++ b/src/main/java/com/iemr/common/repository/dynamic_form/ModuleRepository.java @@ -0,0 +1,10 @@ +package com.iemr.common.repository.dynamic_form; + +import com.iemr.common.data.dynamic_from.FormModule; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ModuleRepository extends CrudRepository { + +} diff --git a/src/main/java/com/iemr/common/repository/mmuDrugHistory/PrescribedMMUDrugRepository.java b/src/main/java/com/iemr/common/repository/mmuDrugHistory/PrescribedMMUDrugRepository.java new file mode 100644 index 00000000..17eb2c13 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/mmuDrugHistory/PrescribedMMUDrugRepository.java @@ -0,0 +1,11 @@ +package com.iemr.common.repository.mmuDrugHistory; + +import com.iemr.common.data.mmuDrugHistory.PrescribedMMUDrugDetail; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PrescribedMMUDrugRepository extends CrudRepository { + PrescribedMMUDrugDetail findByPrescribedDrugID(Long prescribedDrugID); + +} diff --git a/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackCategoryRepository.java b/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackCategoryRepository.java new file mode 100644 index 00000000..43096490 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackCategoryRepository.java @@ -0,0 +1,34 @@ +/* + * 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.repository.platform_feedback; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import com.iemr.common.data.platform_feedback.FeedbackCategory; + +@Repository +public interface PlatformFeedbackCategoryRepository extends JpaRepository { + Optional findBySlugIgnoreCase(String slug); + List findByActiveTrueOrderByLabelAsc(); +} diff --git a/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackRepository.java b/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackRepository.java new file mode 100644 index 00000000..020a7997 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/platform_feedback/PlatformFeedbackRepository.java @@ -0,0 +1,30 @@ +/* + * 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.repository.platform_feedback; + +import com.iemr.common.data.platform_feedback.Feedback; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PlatformFeedbackRepository extends JpaRepository { +} diff --git a/src/main/java/com/iemr/common/repository/sms/SMSTemplateRepository.java b/src/main/java/com/iemr/common/repository/sms/SMSTemplateRepository.java index e5d741f0..06745eb0 100644 --- a/src/main/java/com/iemr/common/repository/sms/SMSTemplateRepository.java +++ b/src/main/java/com/iemr/common/repository/sms/SMSTemplateRepository.java @@ -22,6 +22,7 @@ package com.iemr.common.repository.sms; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -56,7 +57,9 @@ public List getSMSTemplateByProviderServiceMapIDAndSMSTypeID( @Query("select smsTemplate.dltTemplateId from SMSTemplate smsTemplate where smsTemplate.smsTemplateID = :smsTemplateID and smsTemplate.deleted <> true") public String findDLTTemplateID(@Param("smsTemplateID") Integer smsTemplateID ); - + + Optional findBySmsTemplateName(String smsTemplateName); + SMSTemplate findBySmsTemplateID(Integer smsTemplateID); } diff --git a/src/main/java/com/iemr/common/repository/translation/TranslationRepo.java b/src/main/java/com/iemr/common/repository/translation/TranslationRepo.java new file mode 100644 index 00000000..f6a5dcb0 --- /dev/null +++ b/src/main/java/com/iemr/common/repository/translation/TranslationRepo.java @@ -0,0 +1,13 @@ +package com.iemr.common.repository.translation; + +import com.iemr.common.data.translation.Translation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +@Repository +public interface TranslationRepo extends JpaRepository { + + Optional findByLabelKeyAndIsActive(String labelKey, boolean isActive); + +} diff --git a/src/main/java/com/iemr/common/repository/users/EmployeeSignatureRepo.java b/src/main/java/com/iemr/common/repository/users/EmployeeSignatureRepo.java index e1b343a9..85139ab0 100644 --- a/src/main/java/com/iemr/common/repository/users/EmployeeSignatureRepo.java +++ b/src/main/java/com/iemr/common/repository/users/EmployeeSignatureRepo.java @@ -38,5 +38,6 @@ public interface EmployeeSignatureRepo extends CrudRepository getBeneficiaryListByIDs() {// search by regID @@ -154,6 +151,9 @@ public List getPartialBeneficiaryListByIDs(HashSet benI // search beneficiaries by phone number public List getBeneficiaryListByPhone(String phoneNo, String auth, Boolean is1097) throws IEMRException { + logger.info("Phone no from getBeneficiaryListByPhone: " + phoneNo); + String cleanedPhoneNo = cleanPhoneNumber(phoneNo); + logger.info("Cleaned phone no: " + cleanedPhoneNo); List listBenDetailForOutboundDTO = new ArrayList<>(); @@ -165,8 +165,12 @@ public List getBeneficiaryListByPhone(String phoneNo, String a if (auth != null) { header.put("Authorization", auth); } + + logger.info("Result="+(ConfigProperties.getPropertyByName("identity-api-url-getByPhoneNum") + .replace(IDENTITY_BASE_URL, (is1097 ? identity1097BaseURL : identityBaseURL))) + cleanedPhoneNo); + result = httpUtils.post((ConfigProperties.getPropertyByName("identity-api-url-getByPhoneNum") - .replace(IDENTITY_BASE_URL, (is1097 ? identity1097BaseURL : identityBaseURL))) + phoneNo, "", header); + .replace(IDENTITY_BASE_URL, (is1097 ? identity1097BaseURL : identityBaseURL))) + cleanedPhoneNo, "", header); OutputResponse identityResponse = InputMapper.gson().fromJson(result, OutputResponse.class); if (identityResponse.getStatusCode() == OutputResponse.USERID_FAILURE) { @@ -186,6 +190,25 @@ public List getBeneficiaryListByPhone(String phoneNo, String a return listBenDetailForOutboundDTO; } + private String cleanPhoneNumber(String phoneNumber) { + if (phoneNumber == null || phoneNumber.trim().isEmpty()) { + return phoneNumber; + } + + String cleaned = phoneNumber.trim(); + + // Remove +91 prefix + if (cleaned.startsWith("+91")) { + cleaned = cleaned.substring(3); + } + // Remove 91 prefix if it's a 12-digit number (91 + 10 digit mobile) + else if (cleaned.startsWith("91") && cleaned.length() == 12) { + cleaned = cleaned.substring(2); + } + + return cleaned.trim(); + } + @Override // search beneficiary by beneficiary id public List getBeneficiaryListByBenID(String benId, String auth, Boolean is1097) @@ -399,23 +422,38 @@ public List getBeneficiaryListByGovId(String identity, String @Override public String getIdentityResponse(String request, String auth, Boolean is1097) throws IEMRException { - String result; HashMap header = new HashMap<>(); if (auth != null) { header.put("Authorization", auth); } - result = httpUtils.post(ConfigProperties.getPropertyByName("identity-api-url-benCreate") - .replace(IDENTITY_BASE_URL, (is1097 ? identity1097BaseURL : identityBaseURL)), request, header); + String apiUrl = ConfigProperties.getPropertyByName("identity-api-url-benCreate") + .replace(IDENTITY_BASE_URL, (is1097 ? identity1097BaseURL : identityBaseURL)); + logger.info("Calling URL: {}", apiUrl); + logger.info("Request Payload: {}", request); - OutputResponse identityResponse = inputMapper.gson().fromJson(result, OutputResponse.class); - if (identityResponse.getStatusCode() == OutputResponse.USERID_FAILURE) { - throw new IEMRException(identityResponse.getErrorMessage()); + result = httpUtils.post(apiUrl, request, header); + + if (result == null || result.isEmpty()) { + logger.error("Empty response from Identity API"); + throw new IEMRException("No response received from Identity API"); } + + try { + OutputResponse identityResponse = inputMapper.gson().fromJson(result, OutputResponse.class); + if (identityResponse.getStatusCode() == OutputResponse.USERID_FAILURE) { + throw new IEMRException(identityResponse.getErrorMessage()); + } + } catch (JsonSyntaxException e) { + logger.error("JSON parsing error: {}", e.getMessage()); + throw new IEMRException("Invalid response format from Identity API"); + } + return result; } + public Integer editIdentityEditDTO(IdentityEditDTO identityEditDTO, String auth, Boolean is1097) throws IEMRException { JsonParser parser = new JsonParser(); @@ -517,8 +555,11 @@ public List generateBeneficiaryIDs(String request, String a if (auth != null) { header.put("Authorization", auth); } + + logger.info("Request to generate ben IDs: " + request); + logger.info("Generating ben IDs API URL: " + BEN_GEN + BEN_GEN_API_URL); result = httpUtils.post(BEN_GEN + BEN_GEN_API_URL, request, header); - +logger.info("Response from generate ben IDs: " + result); OutputResponse identityResponse = inputMapper.gson().fromJson(result, OutputResponse.class); if (identityResponse.getStatusCode() == OutputResponse.USERID_FAILURE) { @@ -540,4 +581,4 @@ public List generateBeneficiaryIDs(String request, String a return listBen; } -} +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java b/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java index 7f6f6266..7d5f1de0 100644 --- a/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java +++ b/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java @@ -30,6 +30,9 @@ import java.util.ArrayList; import java.util.List; +import com.iemr.common.model.beneficiary.RMNCHBeneficiaryDetailsRmnch; +import com.iemr.common.service.welcomeSms.WelcomeBenificarySmsService; +import com.iemr.common.service.welcomeSms.WelcomeBenificarySmsServiceImpl; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,9 +76,18 @@ public class RegisterBenificiaryServiceImpl implements RegisterBenificiaryServic @Autowired IdentityBenEditMapper identityBenEditMapper; + @Autowired + private WelcomeBenificarySmsService welcomeBenificarySmsService; + @Autowired Validator validator; + + + + + + @Autowired OutboundHistoryRepository outboundHistoryRepository; @@ -113,6 +125,8 @@ public Integer updateBenificiary(BeneficiaryModel benificiaryDetails, String aut Integer updatedRows = 0; IdentityEditDTO identityEditDTO = identityBenEditMapper.BenToIdentityEditMapper(benificiaryDetails); setDemographicDetails(identityEditDTO,benificiaryDetails); + + if (benificiaryDetails.getBeneficiaryIdentities() != null && benificiaryDetails.getBeneficiaryIdentities().size() > 0) { @@ -122,10 +136,12 @@ public Integer updateBenificiary(BeneficiaryModel benificiaryDetails, String aut identityEditDTO.setDob(benificiaryDetails.getDOB()); updatedRows = identityBeneficiaryService.editIdentityEditDTO(identityEditDTO, auth, benificiaryDetails.getIs1097()); - + logger.info("updateBen"); + //updateDeathOfBenificiary(benificiaryDetails); return updatedRows; } + private void setDemographicDetails(IdentityEditDTO identityEditDTO, BeneficiaryModel benificiaryDetails) { if(null != benificiaryDetails.getI_bendemographics()) { identityEditDTO.setCommunity(benificiaryDetails.getI_bendemographics().getCommunityName()); @@ -157,6 +173,9 @@ else if(null != benificiaryDetails.getI_bendemographics().getReligion()) else identityEditDTO.setIncomeStatus(benificiaryDetails.getI_bendemographics().getIncomeStatus()); } + if(benificiaryDetails!=null){ + //updateDeathOfBenificiary(benificiaryDetails); + } } @@ -170,17 +189,21 @@ private int updateDemographics(BeneficiaryDemographicsModel i_BenDemographics) { @Override public String save(BeneficiaryModel beneficiaryModel, HttpServletRequest servletRequest) throws Exception { - // logger.info("benificiaryDetails: " + beneficiaryModel); + logger.info("benificiaryDetails: " + beneficiaryModel); CommonIdentityDTO identityDTO = identityMapper.beneficiaryModelCommonIdentityDTO(beneficiaryModel); + setSaveDemographicDetails(identityDTO,beneficiaryModel); - // identityDTO.setOtherFields(beneficiaryModel.getOtherFields()); +// identityDTO.setOtherFields(beneficiaryModel.getOtherFields()); + identityDTO.setIsConsent(beneficiaryModel.getIsConsent()); + identityDTO.setFaceEmbedding(beneficiaryModel.getFaceEmbedding()); identityDTO.setEmergencyRegistration(beneficiaryModel.isEmergencyRegistration()); identityDTO .setBenFamilyDTOs(identityMapper.benPhoneMapListToBenFamilyDTOList(beneficiaryModel.getBenPhoneMaps())); String request = new Gson().toJson(identityDTO); + if (beneficiaryModel.getIs1097() == null) beneficiaryModel.setIs1097(false); @@ -200,10 +223,18 @@ public String save(BeneficiaryModel beneficiaryModel, HttpServletRequest servlet } else { return response.toString(); } + if(beneficiary!=null){ + if(beneficiary.getBenPhoneMaps().get(0).getPhoneNo()!=null){ + welcomeBenificarySmsService.sendWelcomeSMStoBenificiary(beneficiary.getBenPhoneMaps().get(0).getPhoneNo(),beneficiary.getFirstName()+" "+beneficiary.getLastName(),beneficiary.getBeneficiaryID()); + } + } + } return OutputMapper.gson().toJson(beneficiary); } + + private void setSaveDemographicDetails(CommonIdentityDTO identityDTO, BeneficiaryModel beneficiaryModel) { if(null != beneficiaryModel.getI_bendemographics()) { identityDTO.setCommunity(beneficiaryModel.getI_bendemographics().getCommunityName()); @@ -237,6 +268,9 @@ else if(null != beneficiaryModel.getI_bendemographics().getReligion()) else identityDTO.setIncomeStatus(beneficiaryModel.getI_bendemographics().getIncomeStatus()); } + if(beneficiaryModel!=null){ + // updateDeathOfBenificiary(beneficiaryModel); + } } diff --git a/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandler.java b/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandler.java new file mode 100644 index 00000000..234640a8 --- /dev/null +++ b/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandler.java @@ -0,0 +1,14 @@ +package com.iemr.common.service.beneficiaryOTPHandler; + +import com.iemr.common.data.beneficiaryConsent.BeneficiaryConsentRequest; +import com.iemr.common.data.otp.OTPRequestParsor; +import org.json.JSONObject; + +public interface BeneficiaryOTPHandler { + public String sendOTP(BeneficiaryConsentRequest obj) throws Exception; + + public JSONObject validateOTP(BeneficiaryConsentRequest obj) throws Exception; + + public String resendOTP(BeneficiaryConsentRequest obj) throws Exception; + +} diff --git a/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandlerImpl.java b/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandlerImpl.java new file mode 100644 index 00000000..42e0acfe --- /dev/null +++ b/src/main/java/com/iemr/common/service/beneficiaryOTPHandler/BeneficiaryOTPHandlerImpl.java @@ -0,0 +1,236 @@ +/* + * 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.service.beneficiaryOTPHandler; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.primitives.Ints; +import com.iemr.common.data.beneficiaryConsent.BeneficiaryConsentRequest; +import com.iemr.common.data.otp.OTPRequestParsor; +import com.iemr.common.data.sms.SMSTemplate; +import com.iemr.common.data.sms.SMSType; +import com.iemr.common.repository.sms.SMSTemplateRepository; +import com.iemr.common.repository.sms.SMSTypeRepository; +import com.iemr.common.service.otp.OTPHandler; +import com.iemr.common.service.users.IEMRAdminUserServiceImpl; +import com.iemr.common.utils.config.ConfigProperties; +import com.iemr.common.utils.http.HttpUtils; +import org.json.JSONObject; +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.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Service +public class BeneficiaryOTPHandlerImpl implements BeneficiaryOTPHandler { + + @Autowired + HttpUtils httpUtils; + @Autowired + private IEMRAdminUserServiceImpl iEMRAdminUserServiceImpl; + + final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + @Autowired + SMSTemplateRepository smsTemplateRepository; + private LoadingCache otpCache; + + @Autowired + SMSTypeRepository smsTypeRepository; + @Value("${sms-template-name}") + private String smsTemplateName ; + + private String smsTemplate; + @Value("${sms-username}") + private String smsUserName; + + @Value("${sms-password}") + private String smsPassword ; + + @Value("${sms-message-type}") + private String smsMessageType; + + @Value("${sms-entityid}") + private String smsEntityId; + + @Value("${sms-consent-source-address}") + + private String smsConsentSourceAddress ; + @Value("${send-message-url}") + private String SMS_GATEWAY_URL; + + private static final Integer EXPIRE_MIN = 5; + + // Constructor for new object creation + public BeneficiaryOTPHandlerImpl() { + otpCache = CacheBuilder.newBuilder().expireAfterWrite(EXPIRE_MIN, TimeUnit.MINUTES) + .build(new CacheLoader() { + public String load(String key) { + return "0"; + } + }); + } + + /*** + * @param obj + * @return success if OTP sent successfully + */ + @Override + public String sendOTP(BeneficiaryConsentRequest obj) throws Exception { + int otp = generateOTP(obj.getMobNo()); + return sendSMS(otp, obj); + } + + /*** + * @param obj + * @return OTP verification success or failure + * + */ + @Override + public JSONObject validateOTP(BeneficiaryConsentRequest obj) throws Exception { + String cachedOTP = otpCache.get(obj.getMobNo()); + String inputOTPEncrypted = getEncryptedOTP(obj.getOtp()); + + if (cachedOTP.equalsIgnoreCase(inputOTPEncrypted)) { + JSONObject responseObj = new JSONObject(); + responseObj.put("userName", obj.getMobNo()); + responseObj.put("userID", obj.getMobNo()); + + JSONObject responseOBJ = iEMRAdminUserServiceImpl.generateKeyPostOTPValidation(responseObj); + + return responseOBJ; + } else { + throw new Exception("Please enter valid OTP"); + } + + } + + /*** + * @param obj + * @return success if OTP re-sent successfully + */ + @Override + public String resendOTP(BeneficiaryConsentRequest obj) throws Exception { + int otp = generateOTP(obj.getMobNo()); + return sendSMS(otp, obj); + } + + // generate 6 digit random no # + public int generateOTP(String authKey) throws Exception { + String generatedPassword = null; + Random random = SecureRandom.getInstanceStrong(); + int otp = 100000 + random.nextInt(900000); + + generatedPassword = getEncryptedOTP(otp); + + if (otpCache != null) + otpCache.put(authKey, generatedPassword); + else { + BeneficiaryOTPHandlerImpl obj = new BeneficiaryOTPHandlerImpl(); + obj.otpCache.put(authKey, generatedPassword); + } + return otp; + } + + // SHA-256 encoding logic implemented + private String getEncryptedOTP(int otp) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] bytes = md.digest(Ints.toByteArray(otp)); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); + } + + return sb.toString(); + } + + // send SMS to beneficiary + + + public String sendSMS(int otp, BeneficiaryConsentRequest obj) { + logger.info("templateName"+smsTemplateName); + + final RestTemplate restTemplate = new RestTemplate(); + + Optional smsTemplateData = smsTemplateRepository.findBySmsTemplateName(smsTemplateName); + List smsTemplateAllData = (List) smsTemplateRepository.findAll(); + logger.info("AllData"+smsTemplateAllData.stream().toArray()); + + if(smsTemplateData.isPresent()){ + smsTemplate = smsTemplateRepository.findBySmsTemplateID(smsTemplateData.get().getSmsTemplateID()).getSmsTemplate(); + + } + + String sendSMSAPI = SMS_GATEWAY_URL; + logger.info("sms template"+smsTemplate); + + try { + String message = smsTemplate + .replace("$$OTP$$",String.valueOf(otp)); + + // Build payload + Map payload = new HashMap<>(); + payload.put("customerId",smsUserName); + payload.put("destinationAddress", obj.getMobNo()); + payload.put("message", message); + payload.put("sourceAddress", smsConsentSourceAddress); + payload.put("messageType", smsMessageType); + payload.put("dltTemplateId", smsTemplateData.get().getDltTemplateId()); + payload.put("entityId",smsEntityId ); + payload.put("otp", true); + // Set headers + HttpHeaders headers = new HttpHeaders(); + String auth = smsUserName + ":" + smsPassword; + headers.add("Authorization", + "Basic " + Base64.getEncoder().encodeToString(auth.getBytes())); + + headers.setContentType(MediaType.APPLICATION_JSON); + logger.info("payload: "+payload); + HttpEntity> request = new HttpEntity<>(payload, headers); + + // Call API + ResponseEntity response = restTemplate.postForEntity(sendSMSAPI, request, String.class); + logger.info("sms-response:"+response.getBody()); + if(response.getStatusCode().value()==200){ + return "OTP sent successfully on register mobile number"; + }else { + return "Fail"; + + } + + } catch (Exception e) { + return "Error sending SMS: " + e.getMessage(); + } + } + +} diff --git a/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java b/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java index 1a9474ff..c28d41cb 100644 --- a/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java +++ b/src/main/java/com/iemr/common/service/callhandling/BeneficiaryCallServiceImpl.java @@ -1684,8 +1684,11 @@ public List getBeneficiaryListFromMapper(List getAllCalltypes(String request) throws IEMRException - { - CallType provider = inputMapper.gson().fromJson(request, CallType.class); + public List getAllCalltypes(String request) throws IEMRException { + CallType provider = callTypeMapper.fromJson(request, CallType.class); List callTypes = new ArrayList(); Set callTypesArray = new HashSet(); if (provider.getIsInbound() != null && provider.getIsOutbound() != null) diff --git a/src/main/java/com/iemr/common/service/cti/CTIServiceImpl.java b/src/main/java/com/iemr/common/service/cti/CTIServiceImpl.java index 9a792699..81d8953a 100644 --- a/src/main/java/com/iemr/common/service/cti/CTIServiceImpl.java +++ b/src/main/java/com/iemr/common/service/cti/CTIServiceImpl.java @@ -56,6 +56,7 @@ import com.iemr.common.repository.callhandling.BeneficiaryCallRepository; import com.iemr.common.repository.callhandling.IEMRCalltypeRepositoryImplCustom; import com.iemr.common.utils.config.ConfigProperties; +import com.iemr.common.utils.encryption.AESUtil; import com.iemr.common.utils.exception.IEMRException; import com.iemr.common.utils.http.HttpUtils; import com.iemr.common.utils.mapper.InputMapper; @@ -81,6 +82,10 @@ public class CTIServiceImpl implements CTIService { private static final String CUSTOM_API_FAILURE = "0"; private static final String DEFAULT_IP = "0.0.0.0"; + + @Autowired + private AESUtil aesUtil; + @Autowired private BeneficiaryCallRepository beneficiaryCallRepository; @@ -290,9 +295,16 @@ public OutputResponse getLoginKey(String request, String ipAddress) throws IEMRE String serverURL = ConfigProperties.getPropertyByName("cti-server-ip"); AgentLoginKey agentState = objectMapper.readValue(request, AgentLoginKey.class); + String decryptPassword = null; + + String passphrase = ConfigProperties.getPropertyByName("encryption.passphrase"); + + decryptPassword = aesUtil.decrypt(passphrase, agentState.getPassword()); + + ctiURI = ctiURI.replace("CTI_SERVER", serverURL); ctiURI = ctiURI.replace("USERNAME", (agentState.getUsername() != null) ? agentState.getUsername() : ""); - ctiURI = ctiURI.replace("PASSWORD", (agentState.getPassword() != null) ? agentState.getPassword() : ""); + ctiURI = ctiURI.replace("PASSWORD", (decryptPassword != null) ? decryptPassword : ""); logger.info("calling URL " + ctiURI); ctiURI = ctiURI.replace("AGENT_IP", ipAddress); String response = this.callUrl(ctiURI);// httpUtils.get(ctiURI); diff --git a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterService.java b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterService.java new file mode 100644 index 00000000..6d22e59a --- /dev/null +++ b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterService.java @@ -0,0 +1,22 @@ +package com.iemr.common.service.dynamicForm; + +import com.iemr.common.data.dynamic_from.FormDefinition; +import com.iemr.common.data.dynamic_from.FormField; +import com.iemr.common.data.dynamic_from.FormModule; +import com.iemr.common.dto.dynamicForm.FieldDTO; +import com.iemr.common.dto.dynamicForm.FormDTO; +import com.iemr.common.dto.dynamicForm.FormResponseDTO; +import com.iemr.common.dto.dynamicForm.ModuleDTO; + +import java.util.List; + +public interface FormMasterService { + FormModule createModule(ModuleDTO dto); + FormDefinition createForm(FormDTO dto); + List createField(List dto); + FormField updateField(FieldDTO dto); + + FormResponseDTO getStructuredFormByFormId(String formId,String lang); + + void deleteField(Long fieldId); +} diff --git a/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java new file mode 100644 index 00000000..df019de7 --- /dev/null +++ b/src/main/java/com/iemr/common/service/dynamicForm/FormMasterServiceImpl.java @@ -0,0 +1,204 @@ +package com.iemr.common.service.dynamicForm; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.iemr.common.data.dynamic_from.FormDefinition; +import com.iemr.common.data.dynamic_from.FormField; +import com.iemr.common.data.dynamic_from.FormModule; +import com.iemr.common.data.translation.Translation; +import com.iemr.common.dto.dynamicForm.*; +import com.iemr.common.repository.dynamic_form.FieldRepository; +import com.iemr.common.repository.dynamic_form.FormRepository; +import com.iemr.common.repository.dynamic_form.ModuleRepository; +import com.iemr.common.repository.translation.TranslationRepo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class FormMasterServiceImpl implements FormMasterService { + + @Autowired + private ModuleRepository moduleRepo; + @Autowired private FormRepository formRepo; + @Autowired private FieldRepository fieldRepo; + + @Autowired + private TranslationRepo translationRepo; + + @Override + public FormModule createModule(ModuleDTO dto) { + FormModule module = new FormModule(); + module.setModuleName(dto.getModuleName()); + return moduleRepo.save(module); + } + + @Override + public FormDefinition createForm(FormDTO dto) { + FormModule module = moduleRepo.findById(dto.getModuleId()) + .orElseThrow(() -> new IllegalArgumentException("Invalid module ID")); + + FormDefinition form = new FormDefinition(); + form.setFormId(dto.getFormId()); + form.setFormName(dto.getFormName()); + form.setModule(module); + return formRepo.save(form); + } + + @Override + public List createField(List dtoList) { + List savedFields = new ArrayList<>(); + + for (FieldDTO dto : dtoList) { + FormDefinition form = formRepo.findByFormId(dto.getFormId()) + .orElseThrow(() -> new IllegalArgumentException("Invalid form ID")); + + FormField field = new FormField(); + field.setForm(form); + field.setSectionTitle(dto.getSectionTitle()); + field.setFieldId(dto.getFieldId()); + field.setLabel(dto.getLabel()); + field.setType(dto.getType()); + field.setIsVisible(dto.getIsVisible()); + field.setIsRequired(dto.getIsRequired()); + field.setDefaultValue(dto.getDefaultValue()); + field.setPlaceholder(dto.getPlaceholder()); + field.setOptions(dto.getOptions()); + field.setValidation(dto.getValidation()); + field.setConditional(dto.getConditional()); + field.setSequence(dto.getSequence()); + + savedFields.add(fieldRepo.save(field)); + } + + return savedFields; + } + + @Override + public FormField updateField(FieldDTO dto) { + FormField field = fieldRepo.findById(dto.getId()) + .orElseThrow(() -> new IllegalArgumentException("Field not found: " + dto.getId())); + field.setId(dto.getId()); + field.setSectionTitle(dto.getSectionTitle()); + field.setLabel(dto.getLabel()); + field.setType(dto.getType()); + field.setIsVisible(dto.getIsVisible()); + field.setIsRequired(dto.getIsRequired()); + field.setDefaultValue(dto.getDefaultValue()); + field.setPlaceholder(dto.getPlaceholder()); + field.setSequence(dto.getSequence()); + field.setOptions(dto.getOptions()); + field.setValidation(dto.getValidation()); + field.setConditional(dto.getConditional()); + + + return fieldRepo.save(field); + } + + @Override + public FormResponseDTO getStructuredFormByFormId(String formId,String lang) { + FormDefinition form = formRepo.findByFormId(formId) + .orElseThrow(() -> new IllegalArgumentException("Invalid form ID")); + + List fields = fieldRepo.findByForm_FormIdOrderBySequenceAsc(formId); + ObjectMapper objectMapper = new ObjectMapper(); + + List fieldDtos = fields.stream() + .map(field -> { + String labelKey = field.getFieldId(); // field label already contains label_key + + Translation t = translationRepo.findByLabelKeyAndIsActive(labelKey, true) + .orElse(null); + + String translatedLabel = field.getLabel(); // fallback + + if (t != null) { + if ("hi".equalsIgnoreCase(lang)) { + translatedLabel = t.getHindiTranslation(); + } else { + translatedLabel = t.getEnglish(); + } + } + + FieldResponseDTO dto = new FieldResponseDTO(); + dto.setId(field.getId()); + dto.setVisible(field.getIsVisible()); + dto.setFormId(field.getForm().getFormId()); + dto.setSectionTitle(field.getSectionTitle()); + dto.setFieldId(field.getFieldId()); + dto.setLabel(translatedLabel); + dto.setType(field.getType()); + dto.setIsRequired(field.getIsRequired()); + dto.setDefaultValue(field.getDefaultValue()); + dto.setPlaceholder(field.getPlaceholder()); + dto.setSequence(field.getSequence()); + + try { + // Handle options + if (field.getOptions() != null && !field.getOptions().isBlank()) { + JsonNode node = objectMapper.readTree(field.getOptions()); + List options = null; + if (node.isArray()) { + options = objectMapper.convertValue(node, new TypeReference<>() {}); + } else if (node.has("options")) { + options = objectMapper.convertValue(node.get("options"), new TypeReference<>() {}); + } + dto.setOptions(options == null || options.isEmpty() ? null : options); + } else { + dto.setOptions(null); + } + + // Handle validation + if (field.getValidation() != null && !field.getValidation().isBlank()) { + Map validation = objectMapper.readValue(field.getValidation(), new TypeReference<>() {}); + dto.setValidation(validation.isEmpty() ? null : validation); + } else { + dto.setValidation(null); + } + + // Handle conditional + if (field.getConditional() != null && !field.getConditional().isBlank()) { + Map conditional = objectMapper.readValue(field.getConditional(), new TypeReference<>() {}); + dto.setConditional(conditional.isEmpty() ? null : conditional); + } else { + dto.setConditional(null); + } + } catch (Exception e) { + + System.err.println("JSON Parsing Error in field: " + field.getFieldId()); + throw new RuntimeException("Failed to parse JSON for field: " + field.getFieldId(), e); + } + + return dto; + }) + .sorted(Comparator.comparing(FieldResponseDTO::getId)) + .collect(Collectors.toList()); + + + GroupedFieldResponseDTO singleSection = new GroupedFieldResponseDTO(); + singleSection.setSectionTitle(singleSection.getSectionTitle()); // your custom section title + singleSection.setFields(fieldDtos); + + FormResponseDTO response = new FormResponseDTO(); + response.setVersion(form.getVersion()); + response.setFormId(form.getFormId()); + response.setFormName(form.getFormName()); + response.setSections(List.of(singleSection)); + + return response; + } + + + @Override + public void deleteField(Long fieldId) { + fieldRepo.deleteById(fieldId); + } + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java new file mode 100644 index 00000000..7f33c453 --- /dev/null +++ b/src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java @@ -0,0 +1,118 @@ +/* +* 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/. +* +/* +* AMRIT – Accessible Medical Records via Integrated Technology +*/ +package com.iemr.common.service.firebaseNotification; + +import com.google.firebase.FirebaseException; +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.model.notification.NotificationMessage; +import com.iemr.common.model.notification.UserToken; +import com.iemr.common.repo.userToken.UserTokenRepo; +import com.iemr.common.utils.CookieUtil; +import com.iemr.common.utils.JwtUtil; +import com.iemr.common.utils.exception.IEMRException; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.sql.Timestamp; +import java.util.Optional; + +@Service +public class FirebaseNotificationService { + final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired(required = false) + FirebaseMessaging firebaseMessaging; + + @Autowired + private UserTokenRepo userTokenRepo; + + @Autowired + private CookieUtil cookieUtil; + + @Autowired + private JwtUtil jwtUtil; + + private Message message; + + + public String sendNotification(NotificationMessage notificationMessage) { + if (firebaseMessaging == null) { + logger.error("⚠️ Firebase is not configured, skipping notification"); + return null; + } + + Notification notification = Notification.builder().setTitle(notificationMessage.getTitle()).setBody(notificationMessage.getBody()).build(); + + Message message = Message.builder().setTopic(notificationMessage.getToken()).setNotification(notification).putAllData(notificationMessage.getData()).build(); + + + try { + String response = FirebaseMessaging.getInstance().send(message); + + return response; + } catch (FirebaseException e) { + return "Error sending notification"; + + } + } + + public String updateToken(UserToken userToken) { + Optional existingTokenData = userTokenRepo.findById(userToken.getUserId()); + + UserTokenData userTokenData; + + 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); + return "Save Successfully"; + } + + public String getUserToken() throws IEMRException { + HttpServletRequest requestHeader = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) + .getRequest(); + String jwtTokenFromCookie = cookieUtil.getJwtTokenFromCookie(requestHeader); + return userTokenRepo.findById(Integer.parseInt(jwtUtil.getUserIdFromToken(jwtTokenFromCookie))) // because your userId is Long in DB + .map(UserTokenData::getToken) + .orElse(null); // + } + +} diff --git a/src/main/java/com/iemr/common/service/kmfilemanager/KMFileManagerServiceImpl.java b/src/main/java/com/iemr/common/service/kmfilemanager/KMFileManagerServiceImpl.java index 7a24c6da..4297022a 100644 --- a/src/main/java/com/iemr/common/service/kmfilemanager/KMFileManagerServiceImpl.java +++ b/src/main/java/com/iemr/common/service/kmfilemanager/KMFileManagerServiceImpl.java @@ -1,8 +1,8 @@ /* -* AMRIT – Accessible Medical Records via Integrated Technology -* Integrated EHR (Electronic Health Records) Solution +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution * -* Copyright (C) "Piramal Swasthya Management and Research Institute" +* Copyright (C) "Piramal Swasthya Management and Research Institute" * * This file is part of AMRIT. * @@ -34,12 +34,14 @@ import java.util.Objects; import java.util.Set; +import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; @@ -84,6 +86,9 @@ public void setSubCategoryRepository(SubCategoryRepository subCategoryRepository private InputMapper inputMapper = new InputMapper(); + @Value("${allowed.file.extensions}") + private String allowedFileExtensions; + @Override public String getKMFileLists(String request) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); @@ -125,49 +130,84 @@ public String addKMFile(String request) throws IOException, NoSuchAlgorithmExcep return kmFileManagers.toString(); } - + private ArrayList addKMFile(Iterable kmFileManagers) throws IOException, NoSuchAlgorithmException { ArrayList savedFileManagers = new ArrayList(); FileOutputStream newFile = null; FileInputStream fis = null; - try - { - for (KMFileManager kmFileManager : kmFileManagers) { - if (kmFileManager.getFileName() != null && kmFileManager.getProviderServiceMapID() != null - && kmFileManager.getFileContent() != null) { - kmFileManager.setFileName(kmFileManager.getFileName().replace("`", "").replace("'", "").replace("$", "") - .replace("\\", "").replace("/", "").replace("~", "").replace("`", "").replace("!", "") - .replace("@", "").replace("#", "").replace("$", "").replace("%", "").replace("^", "") - .replace("&", "").replace("*", "").replace("(", "").replace(")", "").replace("{", "") - .replace("}", "").replace("[", "").replace("]", "").replace("|", "").replace("\\", "") - .replace(":", "").replace(";", "").replace("-", "").replace("_", "").replace("+", "") - .replace("=", "").replace("\"", "").replace("'", "")); - String tempFilePath = ConfigProperties.getPropertyByName("tempFilePath"); - newFile = new FileOutputStream(tempFilePath + "/" + kmFileManager.getFileName()); - newFile.write(Base64.getDecoder().decode(kmFileManager.getFileContent())); - newFile.flush(); - newFile.close(); - fis = new FileInputStream(tempFilePath + "/" + kmFileManager.getFileName()); - String checksum = DigestUtils.md5DigestAsHex(fis); - fis.close(); - logger.info("File is " + kmFileManager.getFileName()); - logger.info("File size is " + new File(tempFilePath + "/" + kmFileManager.getFileName()).length()); - logger.info("File checksum is " + checksum); - logger.info("File checksum length is " + checksum.length()); - kmFileManager.setFileCheckSum(checksum); - kmFileManager.setKmUploadStatus(KM_UPLOADSTATUS_PENDING); - String version = getFileVersion(kmFileManager); - kmFileManager.setVersionNo(version); - String documentPath = kmFileManager.getProviderServiceMapID() + "/"; - if (kmFileManager.getCategoryID() != null) { - documentPath += kmFileManager.getCategoryID() + "/"; - } - if (kmFileManager.getSubCategoryID() != null) { - documentPath += kmFileManager.getSubCategoryID() + "/"; - } - if (kmFileManager.getVanID() != null) - documentPath += kmFileManager.getVanID() + "/"; + try { + for (KMFileManager kmFileManager : kmFileManagers) { + if (kmFileManager.getFileName() != null && kmFileManager.getProviderServiceMapID() != null + && kmFileManager.getFileContent() != null) { + + if (allowedFileExtensions == null || allowedFileExtensions.trim().isEmpty()) { + throw new IllegalStateException( + "Environment variable 'allowed.file.extensions' is not configured or is empty"); + } + List allowedExtensions = Arrays.asList(allowedFileExtensions.split(",")); + logger.info("Allowed extensions: " + allowedExtensions); + + + // Extract extension from fileName + String extensionFromName = FilenameUtils.getExtension(kmFileManager.getFileName()); + logger.info("extensionFromName " + extensionFromName); + + // Normalize payload fileExtension (remove dot if present) + String extensionFromPayload = kmFileManager.getFileExtension(); + logger.info("extensionPayload " + extensionFromPayload); + + if (extensionFromPayload != null && extensionFromPayload.startsWith(".")) { + extensionFromPayload = extensionFromPayload.substring(1); + } + + // Validate extensions + if (extensionFromName == null || extensionFromPayload == null) { + throw new IOException("File extension missing"); + } + + if (!extensionFromName.equalsIgnoreCase(extensionFromPayload)) { + throw new IOException( + "File extension mismatch: " + extensionFromName + " vs " + extensionFromPayload); + } + + if (!allowedExtensions.contains(extensionFromName.toLowerCase())) { + throw new IOException("File extension not allowed: " + extensionFromName); + } + + kmFileManager.setFileName(kmFileManager.getFileName().replace("`", "").replace("'", "") + .replace("$", "") + .replace("\\", "").replace("/", "").replace("~", "").replace("`", "").replace("!", "") + .replace("@", "").replace("#", "").replace("$", "").replace("%", "").replace("^", "") + .replace("&", "").replace("*", "").replace("(", "").replace(")", "").replace("{", "") + .replace("}", "").replace("[", "").replace("]", "").replace("|", "").replace("\\", "") + .replace(":", "").replace(";", "").replace("-", "").replace("_", "").replace("+", "") + .replace("=", "").replace("\"", "").replace("'", "")); + String tempFilePath = ConfigProperties.getPropertyByName("tempFilePath"); + newFile = new FileOutputStream(tempFilePath + "/" + kmFileManager.getFileName()); + newFile.write(Base64.getDecoder().decode(kmFileManager.getFileContent())); + newFile.flush(); + newFile.close(); + fis = new FileInputStream(tempFilePath + "/" + kmFileManager.getFileName()); + String checksum = DigestUtils.md5DigestAsHex(fis); + fis.close(); + logger.info("File is " + kmFileManager.getFileName()); + logger.info("File size is " + new File(tempFilePath + "/" + kmFileManager.getFileName()).length()); + logger.info("File checksum is " + checksum); + logger.info("File checksum length is " + checksum.length()); + kmFileManager.setFileCheckSum(checksum); + kmFileManager.setKmUploadStatus(KM_UPLOADSTATUS_PENDING); + String version = getFileVersion(kmFileManager); + kmFileManager.setVersionNo(version); + String documentPath = kmFileManager.getProviderServiceMapID() + "/"; + if (kmFileManager.getCategoryID() != null) { + documentPath += kmFileManager.getCategoryID() + "/"; + } + if (kmFileManager.getSubCategoryID() != null) { + documentPath += kmFileManager.getSubCategoryID() + "/"; + } + if (kmFileManager.getVanID() != null) + documentPath += kmFileManager.getVanID() + "/"; documentPath += version + "/"; documentPath += kmFileManager.getFileName(); @@ -176,9 +216,9 @@ private ArrayList addKMFile(Iterable kmFileManager if (uuid != null) { kmFileManager.setKmUploadStatus(KM_UPLOADSTATUS_COMPLETED); kmFileManager.setFileUID(uuid); - + kmFileManager.setSubCategoryID(kmFileManager.getSubCategoryID()); - + savedFileManagers.add(kmFileManagerRepository.save(kmFileManager)); if (kmFileManager.getSubCategoryID() != null) { updateSubcategoryFilePath(kmFileManager); @@ -194,7 +234,7 @@ private ArrayList addKMFile(Iterable kmFileManager finally { if(newFile !=null) - newFile.close(); + newFile.close(); if(fis !=null) fis.close(); } @@ -211,7 +251,7 @@ private String getFileVersion(KMFileManager kmFileManager) { List files = kmFileManagerRepository.getKMFileByFileName(kmFileManager.getProviderServiceMapID(), kmFileManager.getFileName()); version = "V" + (files.size() + 1); - + return version; } } diff --git a/src/main/java/com/iemr/common/service/platform_feedback/PlatformFeedbackService.java b/src/main/java/com/iemr/common/service/platform_feedback/PlatformFeedbackService.java new file mode 100644 index 00000000..c4cccb42 --- /dev/null +++ b/src/main/java/com/iemr/common/service/platform_feedback/PlatformFeedbackService.java @@ -0,0 +1,115 @@ +/* + * 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.service.platform_feedback; + +import com.iemr.common.data.platform_feedback.Feedback; +import com.iemr.common.data.platform_feedback.FeedbackCategory; +import com.iemr.common.dto.platform_feedback.CategoryResponse; +import com.iemr.common.dto.platform_feedback.FeedbackRequest; +import com.iemr.common.dto.platform_feedback.FeedbackResponse; +import com.iemr.common.repository.platform_feedback.PlatformFeedbackCategoryRepository; +import com.iemr.common.repository.platform_feedback.PlatformFeedbackRepository; +import com.iemr.common.exception.BadRequestException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Service +public class PlatformFeedbackService { + + private final PlatformFeedbackRepository feedbackRepo; + private final PlatformFeedbackCategoryRepository categoryRepo; + + public PlatformFeedbackService(PlatformFeedbackRepository feedbackRepo, + PlatformFeedbackCategoryRepository categoryRepo) { + this.feedbackRepo = feedbackRepo; + this.categoryRepo = categoryRepo; + } + + @Transactional + public FeedbackResponse submitFeedback(FeedbackRequest req) { + // basic validations + if (req.rating() < 1 || req.rating() > 5) { + throw new BadRequestException("rating must be between 1 and 5"); + } + if (!req.isAnonymous() && req.userId() == null) { + throw new BadRequestException("userId required when isAnonymous=false"); + } + + FeedbackCategory category = resolveCategory(req.categoryId(), req.categorySlug(), req.serviceLine()); + + Feedback fb = new Feedback(); + fb.setFeedbackId(UUID.randomUUID().toString()); + fb.setCreatedAt(LocalDateTime.now()); + fb.setUpdatedAt(LocalDateTime.now()); + fb.setRating(req.rating()); + fb.setComment(req.comment() == null ? "" : req.comment()); + fb.setServiceLine(req.serviceLine()); + fb.setAnonymous(req.isAnonymous()); + fb.setCategory(category); + fb.setUserId(req.userId()); + + feedbackRepo.save(fb); + return new FeedbackResponse(fb.getFeedbackId(), fb.getCreatedAt()); + } + + private FeedbackCategory resolveCategory(String categoryId, String categorySlug, String serviceLine) { + if (categoryId != null && categorySlug != null) { + FeedbackCategory byId = categoryRepo.findById(categoryId) + .orElseThrow(() -> new BadRequestException("invalid categoryId")); + if (!byId.getSlug().equalsIgnoreCase(categorySlug)) { + throw new BadRequestException("categoryId and categorySlug mismatch"); + } + if (!byId.isActive()) throw new BadRequestException("category inactive"); + // optional: check scope matches serviceLine or GLOBAL + return byId; + } + + if (categoryId != null) { + FeedbackCategory byId = categoryRepo.findById(categoryId) + .orElseThrow(() -> new BadRequestException("invalid categoryId")); + if (!byId.isActive()) throw new BadRequestException("category inactive"); + return byId; + } + + if (categorySlug != null) { + FeedbackCategory bySlug = categoryRepo.findBySlugIgnoreCase(categorySlug) + .orElseThrow(() -> new BadRequestException("invalid categorySlug")); + if (!bySlug.isActive()) throw new BadRequestException("category inactive"); + return bySlug; + } + + throw new BadRequestException("categoryId or categorySlug required"); + } + + @Transactional(readOnly = true) + public List listCategories(String serviceLine) { + List all = categoryRepo.findByActiveTrueOrderByLabelAsc(); + // filter by scope or return all; FE can filter further + return all.stream() + .filter(c -> "GLOBAL".equals(c.getScope()) || c.getScope().equalsIgnoreCase(serviceLine)) + .map(c -> new CategoryResponse(c.getCategoryId(), c.getSlug(), c.getLabel(), c.getScope(), c.isActive())) + .toList(); + } +} diff --git a/src/main/java/com/iemr/common/service/scheme/SchemeServiceImpl.java b/src/main/java/com/iemr/common/service/scheme/SchemeServiceImpl.java index 327c46ba..44e1efaa 100644 --- a/src/main/java/com/iemr/common/service/scheme/SchemeServiceImpl.java +++ b/src/main/java/com/iemr/common/service/scheme/SchemeServiceImpl.java @@ -111,7 +111,9 @@ public String getFilePath(KMFileManager kmFileManager) { fileUIDAsURI = dmsProtocol + "://" + userName + ":" + userPassword + "@" + dmsPath + "/Download?uuid=" + fileUID; } - return fileUIDAsURI; + // return fileUIDAsURI; + String message = kmFileManager.getFileUID() ; + return message; } @Override diff --git a/src/main/java/com/iemr/common/service/sms/SMSServiceImpl.java b/src/main/java/com/iemr/common/service/sms/SMSServiceImpl.java index 022fd1f8..efe0d16a 100644 --- a/src/main/java/com/iemr/common/service/sms/SMSServiceImpl.java +++ b/src/main/java/com/iemr/common/service/sms/SMSServiceImpl.java @@ -68,6 +68,7 @@ import com.iemr.common.data.location.States; import com.iemr.common.data.mctshistory.MctsDataReaderDetail; import com.iemr.common.data.mctshistory.MctsOutboundCall; +import com.iemr.common.data.mmuDrugHistory.PrescribedMMUDrugDetail; import com.iemr.common.data.sms.SMSNotification; import com.iemr.common.data.sms.SMSParameters; import com.iemr.common.data.sms.SMSParametersMap; @@ -76,6 +77,7 @@ import com.iemr.common.data.telemedicine.PrescribedDrugDetail; import com.iemr.common.data.users.User; import com.iemr.common.data.videocall.VideoCallParameters; +import com.iemr.common.dto.sms.SMSTemplateDTO; import com.iemr.common.mapper.sms.SMSMapper; import com.iemr.common.model.beneficiary.BeneficiaryModel; import com.iemr.common.model.sms.CreateSMSRequest; @@ -95,6 +97,7 @@ import com.iemr.common.repository.location.LocationDistrictRepository; import com.iemr.common.repository.location.LocationStateRepository; import com.iemr.common.repository.mctshistory.OutboundHistoryRepository; +import com.iemr.common.repository.mmuDrugHistory.PrescribedMMUDrugRepository; import com.iemr.common.repository.sms.SMSNotificationRepository; import com.iemr.common.repository.sms.SMSParameterMapRepository; import com.iemr.common.repository.sms.SMSParameterRepository; @@ -104,10 +107,12 @@ import com.iemr.common.repository.videocall.VideoCallParameterRepository; import com.iemr.common.service.beneficiary.IEMRSearchUserService; import com.iemr.common.utils.CryptoUtil; +import com.iemr.common.utils.InputSanitizer; import com.iemr.common.utils.config.ConfigProperties; import com.iemr.common.utils.http.HttpUtils; import com.iemr.common.utils.mapper.OutputMapper; //import java.util.Date; +import org.springframework.transaction.annotation.Transactional; @Service public class SMSServiceImpl implements SMSService { @@ -162,6 +167,9 @@ public class SMSServiceImpl implements SMSService { @Autowired PrescribedDrugRepository prescribedDrugRepository; + @Autowired + PrescribedMMUDrugRepository prescribedMMUDrugRepository; + @Autowired OutboundHistoryRepository outboundHistoryRepository; @@ -206,20 +214,78 @@ public String updateSMSTemplate(UpdateSMSRequest smsRequest) throws Exception { } @Override + @Transactional(rollbackFor = Exception.class) public String saveSMSTemplate(CreateSMSRequest smsRequest) throws Exception { + // Sanitize inputs before processing + sanitizeInputs(smsRequest); + // Validate template syntax + if (!InputSanitizer.isValidTemplateParameter(smsRequest.getSmsTemplate())) { + throw new IllegalArgumentException("Template contains invalid parameter syntax"); + } SMSTemplate smsTemplate; SMSTemplate request = smsMapper.createRequestToSMSTemplate(smsRequest); - smsTemplate = smsTemplateRepository.save(request); - saveSMSParameterMaps(smsRequest, smsTemplate.getSmsTemplateID()); - smsTemplate = smsTemplateRepository.findBySmsTemplateID(smsTemplate.getSmsTemplateID()); - return OutputMapper.gsonWithoutExposeRestriction().toJson(smsMapper.smsTemplateToResponse(smsTemplate)); + SMSTemplate savedTemplate = smsTemplateRepository.save(request); + + saveSMSParameterMaps(smsRequest, savedTemplate.getSmsTemplateID()); + + + SMSTemplate fetchedTemplate = smsTemplateRepository.findBySmsTemplateID(savedTemplate.getSmsTemplateID()); + + SMSTemplateDTO responseDTO = smsMapper.smsTemplateToDTO(fetchedTemplate); + + return OutputMapper.gsonWithoutExposeRestriction().toJson(responseDTO); + } + + /** + * Sanitize all inputs to prevent XSS, SQL injection, and command injection + */ + private void sanitizeInputs(CreateSMSRequest smsRequest) { + logger.debug("Sanitizing SMS template request inputs"); + + // Sanitize template name + if (smsRequest.getSmsTemplateName() != null) { + smsRequest.setSmsTemplateName( + InputSanitizer.sanitize(smsRequest.getSmsTemplateName()) + ); + } + + // Sanitize template content (preserve ${} but remove dangerous chars) + if (smsRequest.getSmsTemplate() != null) { + smsRequest.setSmsTemplate( + InputSanitizer.sanitizeForXSS(smsRequest.getSmsTemplate()) + ); + } + + // Sanitize parameter maps + if (smsRequest.getSmsParameterMaps() != null) { + for (SMSParameterMapModel param : smsRequest.getSmsParameterMaps()) { + if (param.getSmsParameterName() != null) { + param.setSmsParameterName( + InputSanitizer.sanitize(param.getSmsParameterName()) + ); + } + if (param.getSmsParameterValue() != null) { + param.setSmsParameterValue( + InputSanitizer.sanitize(param.getSmsParameterValue()) + ); + } + } + } + + logger.debug("Input sanitization completed"); } private void saveSMSParameterMaps(CreateSMSRequest smsRequest, Integer smsTemplateID) { List smsParameterMapModels = smsRequest.getSmsParameterMaps(); for (SMSParameterMapModel smsParameterMapModel : smsParameterMapModels) { smsParameterMapModel.setSmsTemplateID(smsTemplateID); - smsParameterMapRepository.save(smsMapper.smsParameterMapModelToSMSParametersMap(smsParameterMapModel)); + SMSParametersMap entity = smsMapper.smsParameterMapModelToSMSParametersMap(smsParameterMapModel); + + if (entity.getCreatedBy() == null) { + entity.setCreatedBy(smsRequest.getCreatedBy()); + } + + SMSParametersMap savedEntity = smsParameterMapRepository.save(entity); } } @@ -636,61 +702,64 @@ private SMSNotification prepareSMS( String methodName = smsParametersMap.getSmsParameter().getDataName(); // DataVariableName String variableValue = ""; switch (paramType) { - case "Beneficiary": - variableValue = getBeneficiaryData(className, methodName, request, beneficiary); - benID = variableValue; - break; - case "Institute": - if (request.getIs1097() == true) { - variableValue = getInstituteData(className, methodName, request, authToken); - } else { - variableValue = getDirectoryserviceData(className, methodName, request); - } - break; - case "User": - variableValue = getUserData(className, methodName, request, authToken); - break; - case "Feedback": - variableValue = getFeedbackData(className, methodName, request, authToken); - break; - case "Prescription": - variableValue = getPrescriptionData(className, methodName, request, beneficiary); - break; - case "Blood on Call": - variableValue = getBloodOnCallData(className, methodName, request, beneficiary); - break; - case "Food Safety Complaint": - variableValue = getFoodSafetyComplaintData(className, methodName, request); - break; - case "Epidemic Outbreak Complaint": - variableValue = getEpidemicComplaintData(className, methodName, request); - break; - case "Grievance Tracking": - variableValue = getGrievanceData(className, methodName, request, authToken, beneficiary); - break; - case "MCTS Call Alert": - variableValue = getMCTSCallAlertData(className, methodName, request); - break; - case "Organ Donation": - variableValue = getOrganDonationData(className, methodName, request); - break; - case "TM Schedule": - variableValue = getSpecializationAndTcDateInfo(className, methodName, request); - break; - case "COVID-19": - variableValue = getCOVIDData(className, methodName, request); - break; - case "IMRMMR": - variableValue = getIMRMMRData(className, methodName, request); - break; - case "104 appointment": - variableValue = getUptsuData(className, methodName, request); - break; - case "Grievance": - variableValue = getGrievanceData(className, methodName, request, authToken, beneficiary); - break; - default: - break; + case "Beneficiary": + variableValue = getBeneficiaryData(className, methodName, request, beneficiary); + benID = variableValue; + break; + case "Institute": + if (request.getIs1097() == true) { + variableValue = getInstituteData(className, methodName, request, authToken); + } else { + variableValue = getDirectoryserviceData(className, methodName, request); + } + break; + case "User": + variableValue = getUserData(className, methodName, request, authToken); + break; + case "Feedback": + variableValue = getFeedbackData(className, methodName, request, authToken); + break; + case "Prescription": + variableValue = getPrescriptionData(className, methodName, request, beneficiary); + break; + case "Blood on Call": + variableValue = getBloodOnCallData(className, methodName, request, beneficiary); + break; + case "Food Safety Complaint": + variableValue = getFoodSafetyComplaintData(className, methodName, request); + break; + case "Epidemic Outbreak Complaint": + variableValue = getEpidemicComplaintData(className, methodName, request); + break; + case "Grievance Tracking": + variableValue = getGrievanceData(className, methodName, request, authToken, beneficiary); + break; + case "MCTS Call Alert": + variableValue = getMCTSCallAlertData(className, methodName, request); + break; + case "Organ Donation": + variableValue = getOrganDonationData(className, methodName, request); + break; + case "TM Schedule": + variableValue = getSpecializationAndTcDateInfo(className, methodName, request); + break; + case "COVID-19": + variableValue = getCOVIDData(className, methodName, request); + break; + case "IMRMMR": + variableValue = getIMRMMRData(className, methodName, request); + break; + case "104 appointment": + variableValue = getUptsuData(className, methodName, request); + break; + case "Grievance": + variableValue = getGrievanceData(className, methodName, request, authToken, beneficiary); + break; + case "MMUPrescription": + variableValue = getMMUPrescriptionData(className, methodName, request, beneficiary); + break; + default: + break; } if (variable.equalsIgnoreCase("SMS_PHONE_NO")) { if (request.getIsBloodBankSMS() == true) { @@ -1256,6 +1325,108 @@ private String getEpidemicComplaintData(String className, String methodName, SMS return variableValue; } + private String getMMUPrescriptionData(String className, String methodName, SMSRequest request, + BeneficiaryModel beneficiary) throws NoSuchMethodException, SecurityException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, ClassNotFoundException { + PrescribedMMUDrugDetail prescribedMMUDrug = prescribedMMUDrugRepository + .findByPrescribedDrugID(request.getPrescribedDrugID()); + String variableValue = ""; + switch (methodName.toLowerCase()) { + case "diagnosis": + // Format: "Diagnosis, DrugName(DrugStrength)" + StringBuilder diagnosisBuilder = new StringBuilder(); + + // Add diagnosis + String diagnosis = prescribedMMUDrug.getPrescription() != null + && prescribedMMUDrug.getPrescription().getDiagnosisProvided() != null + ? prescribedMMUDrug.getPrescription().getDiagnosisProvided().trim() + : ""; + + if (!diagnosis.isEmpty()) { + diagnosisBuilder.append(diagnosis); + } + + // Add drug name with strength + String drugName = prescribedMMUDrug.getDrugName(); + String drugStrength = prescribedMMUDrug.getDrugStrength(); + + if (drugName != null && !drugName.trim().isEmpty()) { + if (diagnosisBuilder.length() > 0) { + diagnosisBuilder.append(", "); + } + diagnosisBuilder.append(drugName.trim()); + + if (drugStrength != null && !drugStrength.trim().isEmpty()) { + diagnosisBuilder.append(" (").append(drugStrength.trim()).append(")"); + } + } + + variableValue = diagnosisBuilder.toString(); + break; + case "dosage": + // Format: "duration unit, dose, frequency" + StringBuilder dosageBuilder = new StringBuilder(); + + // Add duration with unit + String duration = prescribedMMUDrug.getDuration(); + String unit = prescribedMMUDrug.getUnit(); + + if (duration != null && !duration.trim().isEmpty()) { + dosageBuilder.append(duration.trim()); + if (unit != null && !unit.trim().isEmpty()) { + dosageBuilder.append(" ").append(unit.trim()); + } + } + + // Add dose + String dose = prescribedMMUDrug.getDose(); + if (dose != null && !dose.trim().isEmpty()) { + if (dosageBuilder.length() > 0) { + dosageBuilder.append(", "); + } + dosageBuilder.append(dose.trim()); + } + + // Add frequency + String frequency = prescribedMMUDrug.getFrequency(); + if (frequency != null && !frequency.trim().isEmpty()) { + if (dosageBuilder.length() > 0) { + dosageBuilder.append(", "); + } + dosageBuilder.append(frequency.trim()); + } + + variableValue = dosageBuilder.toString(); + break; + + case "by": + // Format: "route, By: CreatedBy" + StringBuilder doctorBuilder = new StringBuilder(); + + // Add route + String route = prescribedMMUDrug.getRoute(); + if (route != null && !route.trim().isEmpty()) { + doctorBuilder.append(route.trim()); + } + + // Add created by + String createdBy = prescribedMMUDrug.getCreatedBy(); + if (createdBy != null && !createdBy.trim().isEmpty()) { + if (doctorBuilder.length() > 0) { + doctorBuilder.append(", "); + } + doctorBuilder.append("By: ").append(createdBy.trim()); + } + + variableValue = doctorBuilder.toString(); + break; + default: + break; + } + return variableValue; + } + + private String getGrievanceData(String className, String methodName, SMSRequest request, String authToken, BeneficiaryModel beneficiary) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { diff --git a/src/main/java/com/iemr/common/service/users/EmployeeSignatureService.java b/src/main/java/com/iemr/common/service/users/EmployeeSignatureService.java index a38aa39a..f5fb6709 100644 --- a/src/main/java/com/iemr/common/service/users/EmployeeSignatureService.java +++ b/src/main/java/com/iemr/common/service/users/EmployeeSignatureService.java @@ -27,5 +27,6 @@ public interface EmployeeSignatureService { public EmployeeSignature fetchSignature(Long userID); + public EmployeeSignature fetchActiveSignature(Long userSignID); } diff --git a/src/main/java/com/iemr/common/service/users/EmployeeSignatureServiceImpl.java b/src/main/java/com/iemr/common/service/users/EmployeeSignatureServiceImpl.java index dd2d61ed..115970ed 100644 --- a/src/main/java/com/iemr/common/service/users/EmployeeSignatureServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/EmployeeSignatureServiceImpl.java @@ -41,6 +41,12 @@ public EmployeeSignature fetchSignature(Long userSignID) { return employeeSignatureRepo.findOneByUserID(userSignID); } + @Override + public EmployeeSignature fetchActiveSignature(Long userSignID) { + // New method - fetches only non-deleted records + return employeeSignatureRepo.findOneByUserIDAndDeleted(userSignID, false); + } + public Boolean existSignature(Long userID) { // TODO Auto-generated method stub return employeeSignatureRepo.countByUserIDAndSignatureNotNull(userID)>0; 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 89e238c0..d7dc6e2e 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java @@ -121,6 +121,8 @@ public List getUserServiceRoleMappingForProvider(Integ List getUserServiceRoleMapping(Long userID) throws IEMRException; + List getUserIdbyUserName(String userName) throws IEMRException; + } 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 05854843..44bd2247 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -1218,4 +1218,10 @@ public User getUserById(Long userId) throws IEMRException { throw new IEMRException("Error fetching user with ID: " + userId, e); } } + + @Override + public List getUserIdbyUserName(String userName) { + + return iEMRUserRepositoryCustom.findByUserName(userName); + } } diff --git a/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsService.java b/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsService.java new file mode 100644 index 00000000..9a0eeed6 --- /dev/null +++ b/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsService.java @@ -0,0 +1,9 @@ +package com.iemr.common.service.welcomeSms; + +import org.springframework.stereotype.Service; + +@Service +public interface WelcomeBenificarySmsService { + public String sendWelcomeSMStoBenificiary(String contactNo,String beneficiaryName,String beneficiaryId); + +} diff --git a/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsServiceImpl.java b/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsServiceImpl.java new file mode 100644 index 00000000..67b642ab --- /dev/null +++ b/src/main/java/com/iemr/common/service/welcomeSms/WelcomeBenificarySmsServiceImpl.java @@ -0,0 +1,103 @@ +package com.iemr.common.service.welcomeSms; + +import com.google.common.cache.LoadingCache; +import com.iemr.common.data.sms.SMSTemplate; +import com.iemr.common.repository.sms.SMSTemplateRepository; +import com.iemr.common.repository.sms.SMSTypeRepository; +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.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Service +public class WelcomeBenificarySmsServiceImpl implements WelcomeBenificarySmsService { + final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + @Value("${sms-username}") + private String smsUserName; + + @Value("${sms-password}") + private String smsPassword; + + @Value("${sms-entityid}") + private String smsEntityId; + + @Value("${sms-consent-source-address}") + private String smsSourceAddress; + + @Value("${send-message-url}") + private String SMS_GATEWAY_URL; + + @Autowired + SMSTemplateRepository smsTemplateRepository; + + @Autowired + SMSTypeRepository smsTypeRepository; + + private String smsTemplateName = "welcome_sms"; + + private String smsTemplate; + + @Override + public String sendWelcomeSMStoBenificiary(String contactNo, String beneficiaryName, String beneficiaryId) { + final RestTemplate restTemplate = new RestTemplate(); + + Optional smsTemplateData = smsTemplateRepository.findBySmsTemplateName(smsTemplateName); + if (smsTemplateData.isPresent()) { + smsTemplate = smsTemplateRepository.findBySmsTemplateID(smsTemplateData.get().getSmsTemplateID()).getSmsTemplate(); + + } + + logger.info("sms template" + smsTemplate); + + + String sendSMSAPI = SMS_GATEWAY_URL; + + try { + + String message = smsTemplate.replace("$$BENE_NAME$$", beneficiaryName).replace("$$BEN_ID$$", beneficiaryId); + // Build payload + Map payload = new HashMap<>(); + payload.put("customerId", smsUserName); + payload.put("destinationAddress", contactNo); + payload.put("message", message); + payload.put("sourceAddress", smsSourceAddress); + payload.put("messageType", "SERVICE_IMPLICIT"); + payload.put("dltTemplateId", smsTemplateData.get().getDltTemplateId()); + payload.put("entityId", smsEntityId); + // Set headers + HttpHeaders headers = new HttpHeaders(); + String auth = smsUserName + ":" + smsPassword; + headers.add("Authorization", + "Basic " + Base64.getEncoder().encodeToString(auth.getBytes())); + + headers.setContentType(MediaType.APPLICATION_JSON); + logger.info("payload: " + payload); + HttpEntity> request = new HttpEntity<>(payload, headers); + + // Call API + ResponseEntity response = restTemplate.postForEntity(sendSMSAPI, request, String.class); + logger.info("sms-response:" + response.getBody()); + if (response.getStatusCode().value() == 200) { + return "OTP sent successfully on register mobile number"; + } else { + return "Fail"; + + } + + } catch (Exception e) { + return "Error sending SMS: " + e.getMessage().toString(); + } + + } +} diff --git a/src/main/java/com/iemr/common/utils/CookieUtil.java b/src/main/java/com/iemr/common/utils/CookieUtil.java index 12da4137..92c071c5 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 (1 day) - cookie.setMaxAge(60 * 60 * 24); // 1 day expiration + // Set the Max-Age (expiry time) in seconds (8 hours) + cookie.setMaxAge(60 * 60 * 8); // 8 hours expiration // Set the path to "/" so the cookie is available across the entire application cookie.setPath("/"); diff --git a/src/main/java/com/iemr/common/utils/FilterConfig.java b/src/main/java/com/iemr/common/utils/FilterConfig.java index 9f6efb13..42bd04ad 100644 --- a/src/main/java/com/iemr/common/utils/FilterConfig.java +++ b/src/main/java/com/iemr/common/utils/FilterConfig.java @@ -1,28 +1,102 @@ +/* + * 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 com.iemr.common.filter.PlatformFeedbackRateLimitFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.data.redis.core.StringRedisTemplate; @Configuration public class FilterConfig { - @Value("${cors.allowed-origins}") - private String allowedOrigins; + private static final Logger log = LoggerFactory.getLogger(FilterConfig.class); + + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Bean + public FilterRegistrationBean jwtUserIdValidationFilter( + JwtAuthenticationUtil jwtAuthenticationUtil) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + + // Pass allowedOrigins explicitly to the filter constructor + JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins); + + registrationBean.setFilter(filter); + registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); + registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints + log.info("Registered JwtUserIdValidationFilter on /* with order {}", Ordered.HIGHEST_PRECEDENCE); + return registrationBean; + } + + /** + * Register the platform feedback rate-limit filter in a non-invasive way. + * + * - The filter is mapped only to the public feedback endpoints to avoid affecting other routes. + * - Order is intentionally set after the Jwt filter so authentication runs first. + */ + @Bean + public FilterRegistrationBean platformFeedbackRateLimitFilter( + StringRedisTemplate stringRedisTemplate, + Environment env) { + + // Read flag from environment (property file or env var) + boolean enabled = Boolean.parseBoolean(env.getProperty("platform.feedback.ratelimit.enabled", "false")); + + // Allow optional override for order if needed + int defaultOrder = Ordered.HIGHEST_PRECEDENCE + 10; + int order = defaultOrder; + String orderStr = env.getProperty("platform.feedback.ratelimit.order"); + if (orderStr != null) { + try { + order = Integer.parseInt(orderStr); + } catch (NumberFormatException e) { + log.warn("Invalid platform.feedback.ratelimit.order value '{}', using default {}", orderStr, defaultOrder); + } + } + + PlatformFeedbackRateLimitFilter filter = new PlatformFeedbackRateLimitFilter(stringRedisTemplate, env); + + FilterRegistrationBean reg = new FilterRegistrationBean<>(filter); + + reg.addUrlPatterns( + "/platform-feedback/*", + "/platform-feedback" + ); - @Bean - public FilterRegistrationBean jwtUserIdValidationFilter( - JwtAuthenticationUtil jwtAuthenticationUtil) { - FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + reg.setOrder(order); + // Do not remove the bean from context when disabled; keep registration but disable execution + reg.setEnabled(enabled); - // Pass allowedOrigins explicitly to the filter constructor - JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins); + log.info("Registered PlatformFeedbackRateLimitFilter (enabled={}, order={}) mapped to {}", + enabled, order, reg.getUrlPatterns()); - registrationBean.setFilter(filter); - registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); - registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints - return registrationBean; - } -} + return reg; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/utils/InputSanitizer.java b/src/main/java/com/iemr/common/utils/InputSanitizer.java new file mode 100644 index 00000000..8c67f4fb --- /dev/null +++ b/src/main/java/com/iemr/common/utils/InputSanitizer.java @@ -0,0 +1,110 @@ +package com.iemr.common.utils; + +import org.springframework.web.util.HtmlUtils; + +/** + * Utility class for sanitizing user inputs to prevent XSS, SQL Injection, and Command Injection + */ +public class InputSanitizer { + + // Characters that are dangerous for XSS + private static final String[] XSS_PATTERNS = { + "", "javascript:", "onerror=", "onload=", + "", "<", "\\", "\n", "\r" + }; + + /** + * Sanitize input to prevent XSS attacks + * @param input User input string + * @return Sanitized string with HTML entities encoded + */ + public static String sanitizeForXSS(String input) { + if (input == null || input.trim().isEmpty()) { + return input; + } + + // HTML encode to neutralize XSS + String sanitized = HtmlUtils.htmlEscape(input); + + // Additional check for common XSS patterns (case-insensitive) + String lowerInput = sanitized.toLowerCase(); + for (String pattern : XSS_PATTERNS) { + if (lowerInput.contains(pattern.toLowerCase())) { + // Remove the dangerous pattern + sanitized = sanitized.replaceAll("(?i)" + pattern, ""); + } + } + + return sanitized; + } + + /** + * Sanitize input to prevent command injection + * Removes shell metacharacters + * @param input User input string + * @return Sanitized string + */ + public static String sanitizeForCommandInjection(String input) { + if (input == null || input.trim().isEmpty()) { + return input; + } + + String sanitized = input; + + // Remove dangerous command injection characters + for (String dangerChar : COMMAND_INJECTION_CHARS) { + sanitized = sanitized.replace(dangerChar, ""); + } + + return sanitized; + } + + /** + * Comprehensive sanitization for general text input + * Combines XSS and command injection protection + * @param input User input string + * @return Sanitized string + */ + public static String sanitize(String input) { + if (input == null) { + return null; + } + + // First remove command injection chars, then sanitize XSS + String sanitized = sanitizeForCommandInjection(input); + sanitized = sanitizeForXSS(sanitized); + + return sanitized.trim(); + } + + /** + * Validate that template parameter syntax is safe + * Allows ${paramName} but prevents ${`command`} style injections + * @param template Template string + * @return true if template is safe + */ + public static boolean isValidTemplateParameter(String template) { + if (template == null || template.trim().isEmpty()) { + return false; + } + + // Check for command injection attempts in template parameters + // Valid: ${userName}, ${age} + // Invalid: ${`ls`}, ${$(whoami)}, ${;rm -rf} + + if (template.contains("${`") || template.contains("$(`") || + template.contains("${$(") || template.contains("${;")) { + return false; + } + + return true; + } +} + + diff --git a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java index 13eaea2f..81d79221 100644 --- a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java @@ -1,7 +1,31 @@ +/* + * 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.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +47,7 @@ public class JwtUserIdValidationFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private final String allowedOrigins; + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, String allowedOrigins) { this.jwtAuthenticationUtil = jwtAuthenticationUtil; @@ -36,29 +61,72 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletResponse response = (HttpServletResponse) servletResponse; String origin = request.getHeader("Origin"); + String method = request.getMethod(); + String uri = request.getRequestURI(); logger.debug("Incoming Origin: {}", origin); + logger.debug("Request Method: {}", method); + logger.debug("Request URI: {}", uri); logger.debug("Allowed Origins Configured: {}", allowedOrigins); - logger.info("Add server authorization header to response"); + + // STEP 1: STRICT Origin Validation - Block unauthorized origins immediately + // For OPTIONS requests, Origin header is required (CORS preflight) + if ("OPTIONS".equalsIgnoreCase(method)) { + if (origin == null) { + logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header"); + return; + } + if (!isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } + } else { + // For non-OPTIONS requests, validate origin if present + if (origin != null && !isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } + } + + // Determine request path/context for later checks + String path = request.getRequestURI(); + String contextPath = request.getContextPath(); + + // STEP 3: Add CORS Headers (only for validated origins) if (origin != null && isOriginAllowed(origin)) { - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); + response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); response.setHeader("Access-Control-Allow-Credentials", "true"); - } else { - logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + response.setHeader("Access-Control-Max-Age", "3600"); + logger.info("Origin Validated | Origin: {} | Method: {} | URI: {}", origin, method, uri); } - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - logger.info("OPTIONS request - skipping JWT validation"); + // STEP 4: Handle OPTIONS Preflight Request + if ("OPTIONS".equalsIgnoreCase(method)) { + // OPTIONS (preflight) - respond with full allowed methods + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); + response.setHeader("Access-Control-Allow-Credentials", "true"); response.setStatus(HttpServletResponse.SC_OK); return; } - String path = request.getRequestURI(); - String contextPath = request.getContextPath(); logger.info("JwtUserIdValidationFilter invoked for path: " + path); + // NEW: if this is a platform-feedback endpoint, treat it as public (skip auth) + // and also ensure we don't clear any user cookies for these requests. + if (isPlatformFeedbackPath(path, contextPath)) { + logger.debug("Platform-feedback path detected - skipping authentication and leaving cookies intact: {}", path); + filterChain.doFilter(servletRequest, servletResponse); + return; + } // Log cookies for debugging Cookie[] cookies = request.getCookies(); if (cookies != null) { @@ -73,8 +141,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } // Log headers for debugging - String jwtTokenFromHeader = request.getHeader("Jwttoken"); - logger.info("JWT token from header: "); + logger.debug("JWT token from header: {}", request.getHeader("Jwttoken") != null ? "present" : "not present"); // Skip authentication for public endpoints if (shouldSkipAuthentication(path, contextPath)) { @@ -127,6 +194,19 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } + /** + * New helper: identifies platform-feedback endpoints so we can treat them + * specially (public + preserve cookies). + */ + private boolean isPlatformFeedbackPath(String path, String contextPath) { + if (path == null) return false; + String normalized = path.toLowerCase(); + String base = (contextPath == null ? "" : contextPath).toLowerCase(); + // match /platform-feedback and anything under it + return normalized.startsWith(base + "/platform-feedback"); + } + + private boolean isOriginAllowed(String origin) { if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { logger.warn("No allowed origins configured or origin is null"); @@ -138,14 +218,15 @@ private boolean isOriginAllowed(String origin) { .anyMatch(pattern -> { String regex = pattern .replace(".", "\\.") - .replace("*", ".*") - .replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port + .replace("*", ".*"); boolean matched = origin.matches(regex); return matched; }); } + + private boolean isMobileClient(String userAgent) { if (userAgent == null) return false; @@ -169,7 +250,8 @@ private boolean shouldSkipAuthentication(String path, String contextPath) { || path.startsWith(contextPath + "/user/saveUserSecurityQuesAns") || path.startsWith(contextPath + "/user/userLogout") || path.startsWith(contextPath + "/user/validateSecurityQuestionAndAnswer") - || path.startsWith(contextPath + "/user/logOutUserFromConcurrentSession"); + || path.startsWith(contextPath + "/user/logOutUserFromConcurrentSession") + || path.startsWith(contextPath + "/user/refreshToken"); } private String getJwtTokenFromCookies(HttpServletRequest request) { diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index dc29018c..d8414968 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -23,9 +23,9 @@ public class JwtUtil { @Value("${jwt.refresh.expiration}") private long REFRESH_EXPIRATION_TIME; - + @Autowired - private TokenDenylist tokenDenylist; + private TokenDenylist tokenDenylist; private SecretKey getSigningKey() { if (SECRET_KEY == null || SECRET_KEY.isEmpty()) { @@ -86,12 +86,12 @@ public Claims validateToken(String token) { try { Claims claims = Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload(); String jti = claims.getId(); - + // Check if token is denylisted (only if jti exists) if (jti != null && tokenDenylist.isTokenDenylisted(jti)) { return null; } - + return claims; } catch (ExpiredJwtException ex) { diff --git a/src/main/java/com/iemr/common/utils/RestTemplateUtil.java b/src/main/java/com/iemr/common/utils/RestTemplateUtil.java index 447ba80f..c8299fe7 100644 --- a/src/main/java/com/iemr/common/utils/RestTemplateUtil.java +++ b/src/main/java/com/iemr/common/utils/RestTemplateUtil.java @@ -10,14 +10,17 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import com.iemr.common.constant.Constants; + import jakarta.servlet.http.HttpServletRequest; public class RestTemplateUtil { private final static Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class); - + public static HttpEntity createRequestEntity(Object body, String authorization) { - - ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()); + + ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder + .getRequestAttributes()); if (servletRequestAttributes == null) { MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8"); @@ -25,29 +28,58 @@ public static HttpEntity createRequestEntity(Object body, String authori return new HttpEntity<>(body, headers); } HttpServletRequest requestHeader = servletRequestAttributes.getRequest(); - String jwtTokenFromCookie = null; + + String jwtTokenFromCookie = extractJwttoken(requestHeader); + + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8"); + if (null != UserAgentContext.getUserAgent()) { + headers.add(HttpHeaders.USER_AGENT, UserAgentContext.getUserAgent()); + } + headers.add(HttpHeaders.AUTHORIZATION, authorization); + if (null != requestHeader.getHeader(Constants.JWT_TOKEN)) { + headers.add(Constants.JWT_TOKEN, requestHeader.getHeader(Constants.JWT_TOKEN)); + } + if (null != jwtTokenFromCookie) { + headers.add(HttpHeaders.COOKIE, "Jwttoken=" + jwtTokenFromCookie); + } + + return new HttpEntity<>(body, headers); + } + + private static String extractJwttoken(HttpServletRequest requestHeader) { + String jwtTokenFromCookie = null; try { jwtTokenFromCookie = CookieUtil.getJwtTokenFromCookie(requestHeader); - + } catch (Exception e) { - logger.error("Error while getting jwtToken from Cookie" + e.getMessage() ); - } - - MultiValueMap headers = new LinkedMultiValueMap<>(); - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8"); - if(null != UserAgentContext.getUserAgent()) { - logger.info("Common-API getting User-Agent as : "+UserAgentContext.getUserAgent()); - headers.add(HttpHeaders.USER_AGENT, UserAgentContext.getUserAgent()); - } - headers.add(HttpHeaders.AUTHORIZATION, authorization); - if(null != requestHeader.getHeader("JwtToken")) { - headers.add("JwtToken",requestHeader.getHeader("JwtToken")); - } - if(null != jwtTokenFromCookie) { - headers.add(HttpHeaders.COOKIE, "Jwttoken=" + jwtTokenFromCookie); - } - - return new HttpEntity<>(body, headers); - } - -} \ No newline at end of file + logger.error("Error while getting jwtToken from Cookie" + e.getMessage()); + } + return jwtTokenFromCookie; + } + + public static void getJwttokenFromHeaders(HttpHeaders headers) { + ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder + .getRequestAttributes()); + if (servletRequestAttributes == null) { + return; + } + HttpServletRequest requestHeader = servletRequestAttributes.getRequest(); + String jwtTokenFromCookie = extractJwttoken(requestHeader); + if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) { + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=utf-8"); + } + if (null != UserAgentContext.getUserAgent()) { + if (!headers.containsKey(HttpHeaders.USER_AGENT)) { + headers.add(HttpHeaders.USER_AGENT, UserAgentContext.getUserAgent()); + } + } + if (null != jwtTokenFromCookie) { + headers.add(HttpHeaders.COOKIE, Constants.JWT_TOKEN + "=" + jwtTokenFromCookie); + } else if (null != requestHeader.getHeader(Constants.JWT_TOKEN)) { + headers.add(Constants.JWT_TOKEN, requestHeader.getHeader(Constants.JWT_TOKEN)); + } + + } + +} diff --git a/src/main/java/com/iemr/common/utils/exception/GlobalExceptionHandler.java b/src/main/java/com/iemr/common/utils/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..c22b9616 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/exception/GlobalExceptionHandler.java @@ -0,0 +1,45 @@ +package com.iemr.common.utils.exception; + +import com.iemr.common.utils.response.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; + +import java.util.stream.Collectors; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("Something went wrong", HttpStatus.INTERNAL_SERVER_ERROR.value(), null)); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationException(MethodArgumentNotValidException ex) { + String errorMessage = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.error("Validation Error: " + errorMessage, HttpStatus.BAD_REQUEST.value(), null)); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgException(IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.error(ex.getMessage(), HttpStatus.BAD_REQUEST.value(), null)); + } + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity> handleResponseStatusException(ResponseStatusException ex) { + int statusCode = ex.getStatusCode().value(); // Get status code value + String message = ex.getReason() != null ? ex.getReason() : ex.getMessage(); + + return ResponseEntity.status(ex.getStatusCode()) + .body(ApiResponse.error(message, statusCode, null)); + } +} diff --git a/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java index 23b0af62..0c609839 100644 --- a/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java @@ -21,21 +21,23 @@ */ package com.iemr.common.utils.http; - +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import javax.ws.rs.core.MediaType; 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.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -import com.iemr.common.utils.response.OutputResponse; import com.iemr.common.utils.sessionobject.SessionObject; import com.iemr.common.utils.validator.Validator; +import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Configuration @@ -45,6 +47,9 @@ public class HTTPRequestInterceptor implements HandlerInterceptor { Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + @Value("${cors.allowed-origins}") + private String allowedOrigins; + @Autowired public void setValidator(Validator validator) { this.validator = validator; @@ -125,12 +130,37 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons break; } } catch (Exception e) { - OutputResponse output = new OutputResponse(); - output.setError(e); - response.getOutputStream().print(output.toString()); - response.setContentType(MediaType.APPLICATION_JSON); - response.setContentLength(output.toString().length()); - response.setHeader("Access-Control-Allow-Origin", "*"); + logger.error("Authorization failed: {}", e.getMessage(), e); + + String errorMessage = e.getMessage(); + if (errorMessage == null || errorMessage.trim().isEmpty()) { + errorMessage = "Unauthorized access or session expired."; + } + + String jsonErrorResponse = "{" + + "\"status\": \"Unauthorized\"," + + "\"statusCode\": 401," + + "\"errorMessage\": \"" + errorMessage.replace("\"", "\\\"") + "\"" + + "}"; + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 + response.setContentType(MediaType.APPLICATION_JSON); + + String origin = request.getHeader("Origin"); + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else if (origin != null) { + logger.warn("CORS headers NOT added for error response | Unauthorized origin: {}", origin); + } + + // Better to use getBytes().length for accurate byte size + byte[] responseBytes = jsonErrorResponse.getBytes(StandardCharsets.UTF_8); + response.setContentLength(responseBytes.length); + + ServletOutputStream out = response.getOutputStream(); + out.write(responseBytes); + out.flush(); status = false; } } @@ -164,4 +194,26 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse resp throws Exception { logger.debug("In afterCompletion Request Completed"); } -} \ No newline at end of file + + /** + * Check if the given origin is allowed based on configured allowedOrigins. + * Uses the same logic as JwtUserIdValidationFilter for consistency. + * + * @param origin The origin to validate + * @return true if origin is allowed, false otherwise + */ + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + return false; + } + + return Arrays.stream(allowedOrigins.split(",")) + .map(String::trim) + .anyMatch(pattern -> { + String regex = pattern + .replace(".", "\\.") + .replace("*", ".*"); + return origin.matches(regex); + }); + } +} diff --git a/src/main/java/com/iemr/common/utils/http/HttpUtils.java b/src/main/java/com/iemr/common/utils/http/HttpUtils.java index 0f308619..4f49e662 100644 --- a/src/main/java/com/iemr/common/utils/http/HttpUtils.java +++ b/src/main/java/com/iemr/common/utils/http/HttpUtils.java @@ -40,6 +40,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import com.iemr.common.utils.RestTemplateUtil; import com.sun.jersey.multipart.FormDataBodyPart; import com.sun.jersey.multipart.FormDataMultiPart; @@ -54,9 +55,6 @@ public class HttpUtils { // @Autowired private HttpStatus status; private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); - - // @Autowired(required = true) - // @Qualifier("hibernateCriteriaBuilder") public HttpUtils() { if (rest == null) { rest = new RestTemplate(); @@ -64,33 +62,22 @@ public HttpUtils() { headers.add("Content-Type", "application/json"); } } - // public HttpUtils() { - // if (rest == null) { - // rest = new RestTemplate(); - // headers = new HttpHeaders(); - // headers.add("Content-Type", "application/json"); - // } - // } - - // @Bean - // public HttpUtils httpUtils() { - // return new HttpUtils(); - // } - + public String get(String uri) { String body; - HttpEntity requestEntity = new HttpEntity("", headers); + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.add("Content-Type", "application/json"); + RestTemplateUtil.getJwttokenFromHeaders(requestHeaders); + HttpEntity requestEntity = new HttpEntity("", requestHeaders); ResponseEntity responseEntity = rest.exchange(uri, HttpMethod.GET, requestEntity, String.class); setStatus((HttpStatus) responseEntity.getStatusCode()); - // if (status == HttpStatus.OK){ body = responseEntity.getBody(); - // }else{ - // responseEntity - // } + return body; } public ResponseEntity getV1(String uri) throws URISyntaxException, MalformedURLException { + RestTemplateUtil.getJwttokenFromHeaders(headers); HttpEntity requestEntity = new HttpEntity("", headers); ResponseEntity responseEntity = rest.exchange(uri, HttpMethod.GET, requestEntity, String.class); return responseEntity; @@ -107,6 +94,7 @@ public String get(String uri, HashMap header) { } else { headers.add("Content-Type", MediaType.APPLICATION_JSON); } + RestTemplateUtil.getJwttokenFromHeaders(headers); HttpEntity requestEntity = new HttpEntity("", headers); ResponseEntity responseEntity = rest.exchange(uri, HttpMethod.GET, requestEntity, String.class); setStatus((HttpStatus) responseEntity.getStatusCode()); @@ -116,6 +104,7 @@ public String get(String uri, HashMap header) { public String post(String uri, String json) { String body; + RestTemplateUtil.getJwttokenFromHeaders(headers); HttpEntity requestEntity = new HttpEntity(json, headers); ResponseEntity responseEntity = rest.exchange(uri, HttpMethod.POST, requestEntity, String.class); setStatus((HttpStatus) responseEntity.getStatusCode()); @@ -129,9 +118,7 @@ public String post(String uri, String data, HashMap header) { if (header.containsKey(headers.AUTHORIZATION)) { headers.add(headers.AUTHORIZATION, header.get(headers.AUTHORIZATION).toString()); } - - // headers.add("Content-Type", MediaType.APPLICATION_JSON); - + RestTemplateUtil.getJwttokenFromHeaders(headers); headers.add("Content-Type", MediaType.APPLICATION_JSON + ";charset=utf-8"); ResponseEntity responseEntity = new ResponseEntity(HttpStatus.BAD_REQUEST); HttpEntity requestEntity; diff --git a/src/main/java/com/iemr/common/utils/mapper/CallTypeMapper.java b/src/main/java/com/iemr/common/utils/mapper/CallTypeMapper.java new file mode 100644 index 00000000..ace82367 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/mapper/CallTypeMapper.java @@ -0,0 +1,43 @@ +package com.iemr.common.utils.mapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +@Service +public class CallTypeMapper { + private static GsonBuilder builder; + private static Gson gsonInstance; + private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + + public CallTypeMapper() { + if (builder == null) { + builder = new GsonBuilder(); + // Only serialize/deserialize fields with @Expose annotation + builder.excludeFieldsWithoutExposeAnnotation(); + + logger.info("CallTypeMapper initialized - Only @Expose fields will be processed"); + } + } + + public static Gson gson() { + if (gsonInstance == null) { + gsonInstance = builder.create(); + } + return gsonInstance; + } + + public T fromJson(String json, Class classOfT) { + try { + T result = gson().fromJson(json, classOfT); + logger.info("Successfully deserialized to class: {}", classOfT.getSimpleName()); + return result; + } catch (Exception e) { + logger.error("Error deserializing JSON to {}: {}", classOfT.getSimpleName(), e.getMessage(), e); + throw e; + } + } +} diff --git a/src/main/java/com/iemr/common/utils/response/ApiResponse.java b/src/main/java/com/iemr/common/utils/response/ApiResponse.java new file mode 100644 index 00000000..928a2a25 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/response/ApiResponse.java @@ -0,0 +1,31 @@ +package com.iemr.common.utils.response; + +import lombok.Data; + +@Data +public class ApiResponse { + private boolean success; + private String message; + private Integer statusCode; + + private T data; + + public ApiResponse() {} + + public ApiResponse(boolean success, String message, Integer statusCode,T data) { + this.success = success; + this.message = message; + this.data = data; + this.statusCode = statusCode; + } + + public static ApiResponse success(String message,Integer statusCode, T data) { + return new ApiResponse<>(true, message,statusCode, data); + } + + public static ApiResponse error(String message,Integer statusCode, T data) { + return new ApiResponse<>(false, message, statusCode,data); + } + + // getters and setters +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 813637b0..18723465 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -170,6 +170,207 @@ quality-Audit-PageSize=5 failedLoginAttempt=5 #Jwt Token configuration -jwt.access.expiration=86400000 +jwt.access.expiration=28800000 jwt.refresh.expiration=604800000 +# local env + + + + +## KM Configuration +km-base-protocol=http +km-username=okmAdmin +km-password=admin +km-base-url=http://localhost:8084/OpenKM +km-base-path=localhost:8084/OpenKM +km-root-path=/okm:personal/users/ +km-guest-user=guest +km-guest-password=guest + +# CTI Config +cti-server-ip=10.208.122.99 +cti-logger_base_url=http://10.208.122.99/logger +sms-gateway-url = + +#Encryption passphrase used by AESUtil for decrypting CTI passwords. +encryption.passphrase=Piramal12Piramal + +# Identity Config +identity-api-url = http://localhost:8094/ +#Verify whether 1097 and identity are same? +identity-1097-api-url = http://localhost:8095/ +##Generate Benificiary Config +genben-api=http://localhost:8092/ + +#### SMS Configuration +send-sms=false +sendSMSUrl = http://localhost:8080/sms/sendSMS +#source-address=AIDSHL +#sms-username= +#sms-password= +#send-message-url= + +###SMS Scheduler configurations +start-sms-scheduler=false +cron-scheduler-sms=0 0/1 * * * ? * + +#### Email Configuration +send-email=false +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username= +spring.mail.password= +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true + +###Email Scheduler configurations +start-email-scheduler=true +cron-scheduler-email=0 0/1 * * * ? * + +##-------------------------------###cti data sync Scheduler configurations------------------------------------------------------ + +start-ctidatasync-scheduler=false +cron-scheduler-ctidatasync=0 0 2 */2 * ? + +##-------------------------------###cti data check with call detail report Scheduler------------------------------------------------------ + +#Runs at everyday 12:10AM +start-ctidatacheck-scheduler=true +cron-scheduler-ctidatacheck=0 10 00 * * * + +##---------------------------------#### Registration schedular for Avni------------------------------------------------------------------------------ + +start-avni-scheduler=false +cron-avni-registration=0 0/1 * * * ? * + +##------------------------------------------------#### Everwell Regsitration Scheduler--------------------------------------------------------------- + +start-registration-scheduler=false +cron-scheduler-registration=0 34 19 * * ? * + +##----------------------------------------------------#everwell data sync----------------------------------------------------------- + +start-everwelldatasync-scheduler=false +cron-scheduler-everwelldatasync=0 0/5 * * * ? * + +##-----------------------------------------------#NHM data dashboard schedular---------------------------------------------------------------- +# run at everyday 12:01AM +start-nhmdashboard-scheduler=true +cron-scheduler-nhmdashboard=0 1 * * * ? * +##----------------------------------------------------#grievance data sync----------------------------------------------------------- + +cron-scheduler-grievancedatasync=0 0/2 * * * ? + +### Redis IP +spring.redis.host=localhost + +##--------------------------------------------------------------------------------------------------------------- +###CTI data sync schedular +cz-duration=40 +##--------------------------------------------------------------------------------------------------------------- +## prescription template +TMprescriptionTemplate=TMPrescription +##--------------------------------------------------------------------------------------------------------------- +##duration for everwell calender +everwellCalendarDuration=15 +##--------------------------------------------------------------------------------------------------------------- +##lonic variables +lonicPageSize=10 +##--------------------------------------------------------------------------------------------------------------- +#snomedcti +snomedCTPageSize=10 +##--------------------------------------------------------------------------------------------------------------- +#call retry count +callRetryConfiguration=3 +##--------------------------------------------------------------------------------------------------------------- +#avni registration Duration +avniRegistrationLimit=7 + +#--------------------------NHM Agent Real Time Data---------------------------- +nhm.agent.real.time.data.url= http://175.101.1.83/apps/utility/alive_api.php +nhm.agent.real.time.data.cron.scheduler=0 */2 * ? * * +nhm.agent.real.time.data.cron.flag=true + +carestream_socket_ip = 192.168.43.39 +carestream_socket_port = 1235 + +## everwell variables +everwellUserName = +everwellPassword = +amritUserName = +amritPassword = + +## everwell API call +everwellAddSupportAction = https://beta-hub.everwell.org/Api/Patients/AddSupportAction +everwellEditDoses = https://beta-hub.everwell.org/Api/Patients/EditManualDoses +everwellEditMissedDoses = https://beta-hub.everwell.org/Api/Patients/EditMissedDoses +everwellGetPatientAdherenceUrl = https://beta-hub.everwell.org/Api/Patients/CurrentMonthMissedAdherence?Page= +everwellEditSecondaryPhoneNo = https://beta-hub.everwell.org/Api/Patients/EditPhoneNumber +everwell1097userAuthenticate = http://localhost:8083/user/userAuthenticate +everwelluserAuthenticate = https://beta-hub.everwell.org/token +everwellRegisterBenficiary = http://localhost:8083/beneficiary/create + + +## LungAssessment credentials +lungAssessmentEmail = +lungAssessmentPassword = + + +## SWASSA APIs +lungAssessmentAdminLogin = http://swaasa.sandbox.swaasa.ai/api/adminLogin +lungAssessmentValidateCough = http://swaasa.sandbox.swaasa.ai/api/verifycough +lungAssessmentStartAssesment = http://swaasa.sandbox.swaasa.ai/api/assessment +lungAssessmentGetAssesment = http://swaasa.sandbox.swaasa.ai/api/getAssessment + +#E- Sanjeevani user authenticate creds +eSanjeevani.url: https://preprod.esanjeevaniopd.xyz/uat/aus/api/ThirdPartyAuth/providerLogin +eSanjeevani.userName: +eSanjeevani.password: +eSanjeevani.salt: 123456 +eSanjeevani.source: 11001 +eSanjeevani.registerPatient: https://preprod.esanjeevaniopd.xyz/uat/ps/api/v1/Patient +eSanjeevani.routeUrl: https://uat.esanjeevani.in/user/signin + +biometric.discover.url = http://127.0.0.1:port/ +biometric.deviceInfo.url = http://127.0.0.1:port/rd/info +biometric.capture.url = http://127.0.0.1:port/rd/capture +eAusadhaUrl=https://dlc.kar.nic.in/e-services/api/DWInstituteInward + +eausadhaAuthorization= +spring.main.allow-bean-definition-overriding=true +spring.main.allow-circular-references=true + +fileBasePath =/Doc +##grievance API call +updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list?page=PageNumber¤tpage=1 +updateGrievanceTransactionDetails=/grsbepro/igemr1097/public/api/v1/grievance_details/ + +## grievance variables +grievanceUserName = +grievancePassword = + +grievanceUserAuthenticate = +grievanceDataSyncDuration = + +springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ +springdoc.swagger-ui.enabled=@env.SWAGGER_UI_ENABLED@ + +isProduction=false +grievanceAllocationRetryConfiguration=3 + +logging.path=logs/ +logging.file.name=logs/common-api.log + +captcha.secret-key= +captcha.verify-url= https://challenges.cloudflare.com/turnstile/v0/siteverify +captcha.enable-captcha=false + +video-call-url = + +allowed.file.extensions=msg,pdf,png,jpeg,doc,docx,xlsx,xls,csv,txt + +##sms details for beneficiary otp cosent +sms-template-name = otp_consent + +