Skip to content

Commit 97f7d5f

Browse files
Merge branch 'release-3.6.1' into feature/form_validation
2 parents 64b2a7e + c162bcc commit 97f7d5f

47 files changed

Lines changed: 1850 additions & 240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/environment/common_ci.properties

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ km-base-path=@env.KM_API_BASE_PATH@
1919
km-root-path=/okm:personal/users/
2020
km-guest-user=@env.KM_GUEST_USER@
2121
km-guest-password=@env.KM_GUEST_PASSWORD@
22+
tempFilePath=@env.TEMP_FILE_PATH@
2223

2324
# CTI Config
2425
cti-server-ip=@env.CTI_SERVER_IP@
@@ -192,4 +193,15 @@ cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@
192193
video-call-url=@env.VIDEO_CALL_URL@
193194
jibri.output.path=@env.JIBRI_OUTPUT_PATH@
194195
video.recording.path=@env.VIDEO_RECORDING_PATH@
196+
197+
platform.feedback.ratelimit.enabled=@env.PLATFORM_FEEDBACK_RATELIMIT_ENABLED@
198+
platform.feedback.ratelimit.pepper=@env.PLATFORM_FEEDBACK_RATELIMIT_PEPPER@
199+
platform.feedback.ratelimit.trust-forwarded-for=@env.PLATFORM_FEEDBACK_RATELIMIT_TRUST_FORWARDED_FOR@
200+
platform.feedback.ratelimit.forwarded-for-header=@env.PLATFORM_FEEDBACK_RATELIMIT_FORWARDED_FOR_HEADER@
201+
platform.feedback.ratelimit.minute-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_MINUTE_LIMIT@
202+
platform.feedback.ratelimit.day-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_DAY_LIMIT@
203+
platform.feedback.ratelimit.user-day-limit=@env.PLATFORM_FEEDBACK_RATELIMIT_USER_DAY_LIMIT@
204+
platform.feedback.ratelimit.fail-window-minutes=@env.PLATFORM_FEEDBACK_RATELIMIT_FAIL_WINDOW_MINUTES@
205+
platform.feedback.ratelimit.backoff-minutes=@env.PLATFORM_FEEDBACK_RATELIMIT_BACKOFF_MINUTES@
195206
generateBeneficiaryIDs-api-url=@env.GEN_BENEFICIARY_IDS_API_URL@
207+

src/main/environment/common_docker.properties

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ everwellRegisterBenficiary = ${COMMON_API_BASE_URL}/beneficiary/create
126126
## LungAssessment credentials
127127
lungAssessmentEmail = ${SWAASA_EMAIL}
128128
lungAssessmentPassword =${SWAASA_PASSWORD}
129-
129+
tempFilePath=${TEMP_FILE_PATH}
130130

131131
## SWASSA APIs
132132
lungAssessmentAdminLogin = ${SWAASA_BASE_URL}/api/adminLogin
@@ -195,4 +195,15 @@ firebase.credential-file=${FIREBASE_CREDENTIAL}
195195
video-call-url=${VIDEO_CALL_URL}
196196
jibri.output.path={JIBRI_OUTPUT_PATH}
197197
video.recording.path={VIDEO_RECORDING_PATH}
198+
199+
# Platform Feedback module
200+
platform.feedback.ratelimit.enabled=${PLATFORM_FEEDBACK_RATELIMIT_ENABLED}
201+
platform.feedback.ratelimit.pepper=${PLATFORM_FEEDBACK_RATELIMIT_PEPPER}
202+
platform.feedback.ratelimit.trust-forwarded-for=${PLATFORM_FEEDBACK_RATELIMIT_TRUST_FORWARDED_FOR}
203+
platform.feedback.ratelimit.forwarded-for-header=${PLATFORM_FEEDBACK_RATELIMIT_FORWARDED_FOR_HEADER}
204+
platform.feedback.ratelimit.minute-limit=${PLATFORM_FEEDBACK_RATELIMIT_MINUTE_LIMIT}
205+
platform.feedback.ratelimit.day-limit=${PLATFORM_FEEDBACK_RATELIMIT_DAY_LIMIT}
206+
platform.feedback.ratelimit.user-day-limit=${PLATFORM_FEEDBACK_RATELIMIT_USER_DAY_LIMIT}
207+
platform.feedback.ratelimit.fail-window-minutes=${PLATFORM_FEEDBACK_RATELIMIT_FAIL_WINDOW_MINUTES}
208+
platform.feedback.ratelimit.backoff-minutes=${PLATFORM_FEEDBACK_RATELIMIT_BACKOFF_MINUTES}
198209
generateBeneficiaryIDs-api-url={GEN_BENEFICIARY_IDS_API_URL}

src/main/environment/common_example.properties

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,21 @@ captcha.enable-captcha=true
208208

209209
cors.allowed-origins=http://localhost:*
210210

211+
# ---Platform Feedback module ---
212+
# Rate limiter OFF locally (no Redis required)
213+
platform.feedback.ratelimit.enabled=true
214+
platform.feedback.ratelimit.pepper=dev-pepper-123 # dummy
215+
216+
# trust forwarded-for locally is harmless (localhost only)
217+
platform.feedback.ratelimit.trust-forwarded-for=false
218+
platform.feedback.ratelimit.forwarded-for-header=X-Forwarded-For
219+
220+
# Optional overrides (not needed if disabled)
221+
platform.feedback.ratelimit.minute-limit=10
222+
platform.feedback.ratelimit.day-limit=100
223+
platform.feedback.ratelimit.user-day-limit=50
224+
platform.feedback.ratelimit.fail-window-minutes=5
225+
platform.feedback.ratelimit.backoff-minutes=15
226+
211227
### generate Beneficiary IDs URL
212228
generateBeneficiaryIDs-api-url=/generateBeneficiaryController/generateBeneficiaryIDs

src/main/java/com/iemr/common/CommonApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.redis.connection.RedisConnectionFactory;
3030
import org.springframework.data.redis.core.RedisTemplate;
3131
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
32+
import org.springframework.scheduling.annotation.EnableAsync;
3233
import org.springframework.scheduling.annotation.EnableScheduling;
3334
import org.springframework.web.client.RestTemplate;
3435

@@ -40,6 +41,7 @@
4041

4142
@SpringBootApplication
4243
@EnableScheduling
44+
@EnableAsync
4345
public class CommonApplication extends SpringBootServletInitializer {
4446

4547
@Bean

src/main/java/com/iemr/common/config/CorsConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
@Configuration
1010
public class CorsConfig implements WebMvcConfigurer {
1111

12-
@Value("${cors.allowed-origin}")
12+
@Value("${cors.allowed-origins}")
1313
private String allowedOrigins;
1414
@Override
1515
public void addCorsMappings(CorsRegistry registry) {

src/main/java/com/iemr/common/config/RedisConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.fasterxml.jackson.databind.SerializationFeature;
3434
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
3535
import com.iemr.common.data.users.User;
36+
import org.springframework.data.redis.core.StringRedisTemplate;
3637

3738
@Configuration
3839
public class RedisConfig {
@@ -57,4 +58,10 @@ public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factor
5758
return template;
5859
}
5960

61+
// new bean for rate limiting & counters
62+
@Bean
63+
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
64+
return new StringRedisTemplate(factory);
65+
}
66+
6067
}

src/main/java/com/iemr/common/controller/beneficiary/BeneficiaryRegistrationController.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.slf4j.LoggerFactory;
3737
import org.springframework.beans.factory.annotation.Autowired;
3838
import org.springframework.web.bind.annotation.RequestBody;
39+
import org.springframework.web.bind.annotation.RequestHeader;
3940
import org.springframework.web.bind.annotation.RequestMapping;
4041
import org.springframework.web.bind.annotation.RequestMethod;
4142
import org.springframework.web.bind.annotation.RestController;
@@ -70,6 +71,8 @@
7071
import com.iemr.common.service.userbeneficiarydata.MaritalStatusService;
7172
import com.iemr.common.service.userbeneficiarydata.StatusService;
7273
import com.iemr.common.service.userbeneficiarydata.TitleService;
74+
import com.iemr.common.utils.CookieUtil;
75+
import com.iemr.common.utils.JwtUtil;
7376
import com.iemr.common.utils.mapper.InputMapper;
7477
import com.iemr.common.utils.mapper.OutputMapper;
7578
import com.iemr.common.utils.response.OutputResponse;
@@ -103,6 +106,8 @@ public class BeneficiaryRegistrationController {
103106
private BeneficiaryOccupationService beneficiaryOccupationService;
104107
private GovtIdentityTypeService govtIdentityTypeService;
105108

109+
@Autowired
110+
private JwtUtil jwtUtil;
106111

107112
@Autowired
108113
public void setBenRelationshipTypeService(BenRelationshipTypeService benRelationshipTypeService) {
@@ -342,6 +347,54 @@ public String searchUserByPhone(
342347
return response.toString();
343348
}
344349

350+
@Operation(summary = "Provide the list of beneficiaries using Elasticsearch")
351+
@RequestMapping(value = "/searchUser", method = RequestMethod.POST, headers = "Authorization")
352+
public String searchUser(@RequestBody String request, HttpServletRequest httpRequest) {
353+
OutputResponse response = new OutputResponse();
354+
try {
355+
logger.info("Universal search request received");
356+
357+
JsonParser parser = new JsonParser();
358+
JsonObject requestObj = parser.parse(request).getAsJsonObject();
359+
360+
String searchQuery = null;
361+
if (requestObj.has("search") && !requestObj.get("search").isJsonNull()) {
362+
searchQuery = requestObj.get("search").getAsString();
363+
}
364+
365+
if (searchQuery == null || searchQuery.trim().isEmpty()) {
366+
response.setError(400, "Search query is required");
367+
return response.toString();
368+
}
369+
370+
String auth = httpRequest.getHeader("Authorization");
371+
372+
Integer userID = jwtUtil.getUserIdFromRequest(httpRequest);
373+
374+
logger.info("ES search for userId: {}", userID);
375+
376+
Boolean is1097 = false;
377+
if (requestObj.has("is1097") && !requestObj.get("is1097").isJsonNull()) {
378+
is1097 = requestObj.get("is1097").getAsBoolean();
379+
}
380+
381+
logger.info("Searching with query: {}, userId: {}, is1097: {}", searchQuery, userID, is1097);
382+
String result = iemrSearchUserService.searchUser(searchQuery, userID, auth, is1097);
383+
384+
if (result == null || result.trim().isEmpty()) {
385+
response.setError(200, "No beneficiaries found");
386+
return response.toString();
387+
}
388+
389+
return result;
390+
391+
} catch (Exception e) {
392+
logger.error("Error in universal search: {}", e.getMessage(), e);
393+
response.setError(400, "Error searching beneficiaries: " + e.getMessage());
394+
return response.toString();
395+
}
396+
}
397+
345398
@Operation(summary = "Provide the list of beneficiaries based on search criteria")
346399
@RequestMapping(value = "/searchBeneficiary", method = RequestMethod.POST, headers = "Authorization")
347400
public String searchBeneficiary(
@@ -364,6 +417,41 @@ public String searchBeneficiary(
364417
return output.toString();
365418
}
366419

420+
/**
421+
* Elasticsearch-based advanced search endpoint
422+
*/
423+
@Operation(summary = "Advanced search beneficiaries using Elasticsearch")
424+
@RequestMapping(value = "/searchBeneficiaryES", method = RequestMethod.POST, headers = "Authorization")
425+
public String searchBeneficiaryES(
426+
@RequestBody BeneficiaryModel request,
427+
HttpServletRequest httpRequest) {
428+
429+
logger.info("searchBeneficiaryES request: {}", request);
430+
OutputResponse output = new OutputResponse();
431+
432+
try {
433+
434+
String auth = httpRequest.getHeader("Authorization");
435+
436+
Integer userID = jwtUtil.getUserIdFromRequest(httpRequest);
437+
438+
logger.info("ES Advanced search for userId: {}", userID);
439+
440+
String result = iemrSearchUserService.findBeneficiaryES(request, userID, auth);
441+
442+
return result;
443+
444+
} catch (NumberFormatException ne) {
445+
logger.error("searchBeneficiaryES failed with number format error: {}", ne.getMessage(), ne);
446+
output.setError(400, "Invalid number format in search criteria");
447+
return output.toString();
448+
} catch (Exception e) {
449+
logger.error("searchBeneficiaryES failed with error: {}", e.getMessage(), e);
450+
output.setError(500, "Error searching beneficiaries: " + e.getMessage());
451+
return output.toString();
452+
}
453+
}
454+
367455
@Operation(summary = "Provide all common data list needed for beneficiary registration")
368456
@RequestMapping(value = "/getRegistrationData", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization")
369457
public String getRegistrationData() {

src/main/java/com/iemr/common/controller/dynamicForm/DynamicFormController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,6 @@ public ResponseEntity<ApiResponse<?>> getStructuredForm(@PathVariable String for
9797
}
9898

9999

100+
101+
100102
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* AMRIT – Accessible Medical Records via Integrated Technology
3+
* Integrated EHR (Electronic Health Records) Solution
4+
*
5+
* Copyright (C) "Piramal Swasthya Management and Research Institute"
6+
*
7+
* This file is part of AMRIT.
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see https://www.gnu.org/licenses/.
21+
*/
22+
package com.iemr.common.controller.platform_feedback;
23+
24+
import com.iemr.common.dto.platform_feedback.CategoryResponse;
25+
import com.iemr.common.dto.platform_feedback.FeedbackRequest;
26+
import com.iemr.common.dto.platform_feedback.FeedbackResponse;
27+
import com.iemr.common.service.platform_feedback.PlatformFeedbackService;
28+
import io.swagger.v3.oas.annotations.Operation;
29+
import io.swagger.v3.oas.annotations.tags.Tag;
30+
import io.swagger.v3.oas.annotations.Parameter;
31+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
32+
import io.swagger.v3.oas.annotations.media.Content;
33+
import org.springframework.http.ResponseEntity;
34+
import org.springframework.http.HttpStatus;
35+
import org.springframework.validation.annotation.Validated;
36+
import jakarta.validation.Valid;
37+
import org.springframework.web.bind.annotation.*;
38+
39+
import java.util.List;
40+
41+
@Tag(name = "Platform Feedback", description = "Feedback ingestion and category listing for platform-wide feedback")
42+
@RestController
43+
@RequestMapping("/platform-feedback")
44+
@Validated
45+
public class PlatformFeedbackController {
46+
47+
private final PlatformFeedbackService service;
48+
49+
public PlatformFeedbackController(PlatformFeedbackService service) {
50+
this.service = service;
51+
}
52+
53+
@Operation(summary = "Submit feedback (public endpoint)",
54+
description = "Accepts feedback (anonymous or identified). Accepts categoryId or categorySlug; slug is preferred.")
55+
@ApiResponse(responseCode = "201", description = "Feedback accepted")
56+
@ApiResponse(responseCode = "400", description = "Validation or business error", content = @Content)
57+
@PostMapping
58+
public ResponseEntity<FeedbackResponse> submit(
59+
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Feedback payload")
60+
@Valid @RequestBody FeedbackRequest req) {
61+
FeedbackResponse resp = service.submitFeedback(req);
62+
return ResponseEntity.status(HttpStatus.CREATED).body(resp);
63+
}
64+
65+
@Operation(summary = "List active categories",
66+
description = "Returns active categories. Optionally filter by serviceLine (frontend convenience).")
67+
@ApiResponse(responseCode = "200", description = "List of categories")
68+
@GetMapping("/categories")
69+
public ResponseEntity<List<CategoryResponse>> list(
70+
@Parameter(description = "Optional serviceLine to prefer scopes (1097|104|AAM|MMU|TM|ECD)")
71+
@RequestParam(required = false) String serviceLine) {
72+
if (serviceLine == null) serviceLine = "GLOBAL";
73+
List<CategoryResponse> list = service.listCategories(serviceLine);
74+
return ResponseEntity.ok(list);
75+
}
76+
}

src/main/java/com/iemr/common/controller/users/IEMRAdminController.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ private void createUserMapping(User mUser, JSONObject resMap, JSONObject service
392392
resMap.put("agentPassword", mUser.getAgentPassword());
393393
resMap.put("m_UserLangMappings", new JSONArray(mUser.getM_UserLangMappings().toString()));
394394
resMap.put("designationID", mUser.getDesignationID());
395+
resMap.put("dhistoken",mUser.getDhistoken());
395396
if (mUser.getDesignation() != null) {
396397
resMap.put("designation", new JSONObject(mUser.getDesignation().toString()));
397398
}
@@ -1223,7 +1224,25 @@ public ResponseEntity<?> getUserDetails(@PathVariable("userName") String userNam
12231224
return new ResponseEntity<>(Map.of("error", "UserName Not Found"), HttpStatus.NOT_FOUND);
12241225
}
12251226
User user = users.get(0);
1226-
return new ResponseEntity<>(Map.of("userName", user.getUserName(), "userId", user.getUserID()), HttpStatus.OK);
1227+
return new ResponseEntity<>(Map.of("userName", user.getUserName(), "userId", user.getUserID()),
1228+
HttpStatus.OK);
1229+
} catch (Exception e) {
1230+
return new ResponseEntity<>(Map.of("error", "Internal server error"), HttpStatus.INTERNAL_SERVER_ERROR);
1231+
}
1232+
1233+
}
1234+
1235+
@Operation(summary = "Get UserId based on userName")
1236+
@GetMapping(value = "/checkUserName/{userName}", produces = MediaType.APPLICATION_JSON, headers = "Authorization")
1237+
public ResponseEntity<?> checkUserDetails(@PathVariable("userName") String userName) {
1238+
try {
1239+
List<User> users = iemrAdminUserServiceImpl.findUserIdByUserName(userName);
1240+
if (users.isEmpty()) {
1241+
return new ResponseEntity<>(Map.of("error", "UserName Not Found"), HttpStatus.NOT_FOUND);
1242+
}
1243+
User user = users.get(0);
1244+
return new ResponseEntity<>(Map.of("userName", user.getUserName(), "userId", user.getUserID()),
1245+
HttpStatus.OK);
12271246
} catch (Exception e) {
12281247
return new ResponseEntity<>(Map.of("error", "Internal server error"), HttpStatus.INTERNAL_SERVER_ERROR);
12291248
}

0 commit comments

Comments
 (0)