Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/main/environment/common_ci.properties
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,7 @@ logging.file.name=@env.MMU_API_LOGGING_FILE_NAME@
springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@
springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@

cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@
cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@

# Server that hosts the failedBeneficiaries API
nikshay.failedBeneficiariesUrl=@env.MMU_CENTRAL_SERVER@nikshay/syncStatus/failedBeneficiaries
5 changes: 4 additions & 1 deletion src/main/environment/common_docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,7 @@ logging.path=logs
logging.file.name=${MMU_API_LOGGING_FILE_NAME}

springdoc.api-docs.enabled=${SWAGGER_DOC_ENABLED}
springdoc.swagger-ui.enabled=${SWAGGER_DOC_ENABLED}
springdoc.swagger-ui.enabled=${SWAGGER_DOC_ENABLED}

# Server that hosts the failedBeneficiaries API
nikshay.failedBeneficiariesUrl=${MMU_CENTRAL_SERVER}nikshay/syncStatus/failedBeneficiaries
2 changes: 2 additions & 0 deletions src/main/environment/common_example.properties

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not put exact ip value it is just blank or Enter failed URL in <>

Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ tmCentralServer = <Enter your socket address here>/tm-api/common/get/Case-sheet/

specialistSign = <Enter your socket address here>/common-api/signature1/getSignClass

nikshay.failedBeneficiariesUrl=http://localhost:8087/mmu-api/nikshay/syncStatus/failedBeneficiaries

fileBasePath =<File base path>

## data sync batch size
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package com.iemr.mmu.controller.nikshay;

import com.google.gson.Gson;
import com.iemr.mmu.data.nikshay.NikshayMissingBeneficiaryDTO;
import com.iemr.mmu.data.nikshay.NikshaySyncStatusUpdateDTO;
import com.iemr.mmu.service.nikshay.NikshaySyncStatusService;
import com.iemr.mmu.utils.NikshayConstants;
import com.iemr.mmu.utils.exception.IEMRException;
import com.iemr.mmu.utils.response.OutputResponse;
import com.iemr.mmu.utils.validator.Validator;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
@RequestMapping(NikshayConstants.NIKSHAY_PATH)
public class NikshaySyncStatusController {

private static final int MAX_PAGE_SIZE = 100;

private final Logger logger = LoggerFactory.getLogger(NikshaySyncStatusController.class);

private final Gson gson = new Gson();

@Autowired

Check warning on line 41 in src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaGcRwGrSb4Sm_3Nrws7&open=AaGcRwGrSb4Sm_3Nrws7&pullRequest=206
private NikshaySyncStatusService nikshaySyncStatusService;

@Autowired

Check warning on line 44 in src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaGcRwGrSb4Sm_3Nrws8&open=AaGcRwGrSb4Sm_3Nrws8&pullRequest=206
private Validator validator;

@PostMapping("/syncStatus/syncFailed")
@Operation(summary = "Copy all FAILED rows from nikshay_beneficiary_phase_status into nikshay_beneficiary_sync_status")
public String syncFailedPhaseStatuses(@RequestHeader(value = "Authorization") String authorization,
@RequestHeader(value = "ServerAuthorization") String serverAuthorization) {
OutputResponse response = new OutputResponse();
try {
Map<String, Object> result = nikshaySyncStatusService.syncFailedPhaseStatuses(serverAuthorization);
response.setResponse(gson.toJson(result));
} catch (Exception e) {
logger.error("Error in syncFailedPhaseStatuses: " + e);

Check warning on line 56 in src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Format specifiers should be used instead of string concatenation.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaGcRwGrSb4Sm_3Nrws9&open=AaGcRwGrSb4Sm_3Nrws9&pullRequest=206
response.setError(5000, "Error syncing failed Nikshay phase statuses: " + e.getMessage());
}
return response.toString();
}

@GetMapping("/syncStatus/missingBeneficiaries")
@Operation(summary = "Get beneficiaries whose Ni-kshay sync failed (paged); all of them with no filter, "
+ "else only those matching providerServiceMapID and/or villageID")
public ResponseEntity<Map<String, Object>> getMissingBeneficiaries(
@RequestParam(value = "providerServiceMapID", required = false) Integer providerServiceMapID,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should missingBeneficiaries and update (line 137) also check ServerAuthorization, like failedBeneficiaries does (line 121)? They return beneficiary names/villages and change data.

@RequestParam(value = "villageID", required = false) Integer villageID,
@RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "30") Integer pageSize) {
logger.info("getMissingBeneficiaries request: providerServiceMapID={}, villageID={}, pageNo={}, pageSize={}",
providerServiceMapID, villageID, pageNo, pageSize);
try {
if (pageNo < 0) {
return errorResponse(HttpStatus.BAD_REQUEST, "pageNo must be 0 or greater");
}
if (pageSize < 1 || pageSize > MAX_PAGE_SIZE) {
return errorResponse(HttpStatus.BAD_REQUEST, "pageSize must be between 1 and " + MAX_PAGE_SIZE);
}

Page<NikshayMissingBeneficiaryDTO> page = nikshaySyncStatusService.getMissingBeneficiaries(
providerServiceMapID, villageID, pageNo, pageSize);

Map<String, Object> pagination = new LinkedHashMap<>();
pagination.put("page", page.getNumber());
pagination.put("size", page.getSize());
pagination.put("totalElements", page.getTotalElements());
pagination.put("totalPages", page.getTotalPages());
pagination.put("hasNext", page.hasNext());

Map<String, Object> body = new LinkedHashMap<>();
body.put("status", "SUCCESS");

Check failure on line 91 in src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "status" 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaGcRwGrSb4Sm_3Nrws6&open=AaGcRwGrSb4Sm_3Nrws6&pullRequest=206
body.put("message", page.isEmpty()

Check failure on line 92 in src/main/java/com/iemr/mmu/controller/nikshay/NikshaySyncStatusController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "message" 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaGcRwGrSb4Sm_3Nrws5&open=AaGcRwGrSb4Sm_3Nrws5&pullRequest=206
? "No missing beneficiary data found"
: "Missing beneficiary data fetched successfully");
body.put("data", page.getContent());
body.put("pagination", pagination);
return ResponseEntity.ok(body);
} catch (Exception e) {
logger.error("Error in getMissingBeneficiaries for providerServiceMapID={}, villageID={}",
providerServiceMapID, villageID, e);
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR,
"Error fetching missing beneficiary data: " + e.getMessage());
}
}

@GetMapping(NikshayConstants.FAILED_BENEFICIARIES_PATH)
@Operation(summary = "Get every beneficiary with a FAILED phase in nikshay_beneficiary_phase_status, with all its phases (not paged)")
public ResponseEntity<Map<String, Object>> getFailedBeneficiaries(
@RequestHeader(value = "ServerAuthorization") String serverAuthorization, HttpServletRequest request) {
// The JWT filter lets java/ and okhttp clients through without a token, and HttpInterceptor skips
// an empty Authorization header, so the header's presence proves nothing. ServerAuthorization must
// be a live login session key (the one /syncFailed sends after the data-sync server login).
if (serverAuthorization == null || serverAuthorization.trim().isEmpty()) {
return errorResponse(HttpStatus.UNAUTHORIZED, "ServerAuthorization is required");
}
String remoteAddress = request.getHeader("X-FORWARDED-FOR");
if (remoteAddress == null || remoteAddress.trim().isEmpty()) {
remoteAddress = request.getRemoteAddr();
}
try {
validator.checkKeyExists(serverAuthorization, remoteAddress);
} catch (IEMRException e) {
return errorResponse(HttpStatus.UNAUTHORIZED, "Invalid or expired ServerAuthorization");
}

try {
return ResponseEntity.ok(nikshaySyncStatusService.getFailedBeneficiaries());
} catch (Exception e) {
logger.error("Error in getFailedBeneficiaries", e);
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR,
"Error fetching failed Nikshay beneficiaries: " + e.getMessage());
}
}

@PostMapping("/syncStatus/update")
@Operation(summary = "Set isUpdated on all nikshay_beneficiary_sync_status rows of a beneficiary")
public ResponseEntity<Map<String, Object>> updateSyncStatus(@RequestBody NikshaySyncStatusUpdateDTO request) {
logger.info("updateSyncStatus request: beneficiaryId={}, isUpdated={}",
request != null ? request.getBeneficiaryId() : null,
request != null ? request.getIsUpdated() : null);
try {
if (request == null || request.getBeneficiaryId() == null) {
return errorResponse(HttpStatus.BAD_REQUEST, "beneficiaryId is required");
}
if (request.getIsUpdated() == null) {
return errorResponse(HttpStatus.BAD_REQUEST, "isUpdated is required");
}

int rows = nikshaySyncStatusService.updateIsUpdatedByBeneficiaryId(
request.getBeneficiaryId(), request.getIsUpdated());
if (rows == 0) {
return errorResponse(HttpStatus.NOT_FOUND,
"No sync status record found for beneficiaryId " + request.getBeneficiaryId());
}

Map<String, Object> data = new LinkedHashMap<>();
data.put("beneficiaryId", request.getBeneficiaryId());
data.put("isUpdated", request.getIsUpdated());
data.put("recordsUpdated", rows);

Map<String, Object> body = new LinkedHashMap<>();
body.put("status", "SUCCESS");
body.put("message", "Update done for beneficiaryId " + request.getBeneficiaryId());
body.put("data", data);
return ResponseEntity.ok(body);
} catch (Exception e) {
logger.error("Error in updateSyncStatus for beneficiaryId={}",
request != null ? request.getBeneficiaryId() : null, e);
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR,
"Error updating Nikshay sync status: " + e.getMessage());
}
}

private ResponseEntity<Map<String, Object>> errorResponse(HttpStatus httpStatus, String message) {
if (httpStatus.is4xxClientError()) {
logger.warn("Nikshay sync status request rejected ({}): {}", httpStatus.value(), message);
}
Map<String, Object> body = new LinkedHashMap<>();
body.put("status", "FAILED");
body.put("message", message);
body.put("data", new ArrayList<>());
return ResponseEntity.status(httpStatus).body(body);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/iemr/mmu/data/nikshay/BeneficiaryNameRow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.iemr.mmu.data.nikshay;

// One row of NikshayBeneficiarySyncStatusRepo.findBeneficiaryNames; column aliases match the getters
public interface BeneficiaryNameRow {

Number getBenRegID();

String getFirstName();

String getLastName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.iemr.mmu.data.nikshay;

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;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.sql.Timestamp;

@Entity
@Table(name = "nikshay_beneficiary_phase_status", schema = "db_iemr")
@Data
public class NikshayBeneficiaryPhaseStatus {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "ben_reg_id", nullable = false)
private Long benRegID;

// BeneficiaryID from db_identity.m_beneficiaryregidmapping; null if not found
@Column(name = "beneficiary_id")
private Long beneficiaryID;

// INDEX | CONTACT | ALL_NEGATIVE
@Column(name = "beneficiary_type", length = 20, nullable = false)
private String beneficiaryType;

// For a CONTACT: BenRegId of their index patient
@Column(name = "index_ben_reg_id")
private Long indexBenRegID;

// ENROLMENT | CONTACTS | XRAY_UPLOAD | NAAT_UPLOAD | TRANSFER_OUT
@Column(name = "phase", length = 20, nullable = false)
private String phase;

// PENDING | IN_PROGRESS | DONE | FAILED | NOT_APPLICABLE
@Column(name = "status", length = 20, nullable = false)
private String status;

@Column(name = "failure_reason", length = 50)
private String failureReason;

@Column(name = "error_detail", length = 2000)
private String errorDetail;

@Column(name = "attempts", nullable = false)
private Integer attempts = 0;

@Column(name = "nikshay_id", length = 50)
private String nikshayId;

@Column(name = "episode_id", length = 50)
private String episodeId;

@Column(name = "last_attempt_at")
private Timestamp lastAttemptAt;

@Column(name = "completed_at")
private Timestamp completedAt;

@CreationTimestamp
@Column(name = "created_at", updatable = false)
private Timestamp createdAt;

@UpdateTimestamp
@Column(name = "updated_at")
private Timestamp updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.iemr.mmu.data.nikshay;

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;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.sql.Timestamp;

@Entity
@Table(name = "nikshay_beneficiary_sync_status", schema = "db_iemr")
Comment thread
PankajUnthin marked this conversation as resolved.
Resolved
@Data
public class NikshayBeneficiarySyncStatus {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "ben_reg_id", nullable = false)
private Long benRegID;

// BeneficiaryID from db_identity.m_beneficiaryregidmapping; null if not found
@Column(name = "beneficiary_id")
private Long beneficiaryID;

// INDEX | CONTACT | ALL_NEGATIVE
@Column(name = "beneficiary_type", length = 20, nullable = false)
private String beneficiaryType;

// For a CONTACT: BenRegId of their index patient
@Column(name = "index_ben_reg_id")
private Long indexBenRegID;

// ENROLMENT | CONTACTS | XRAY_UPLOAD | NAAT_UPLOAD | TRANSFER_OUT
@Column(name = "phase", length = 20, nullable = false)
private String phase;

// PENDING | IN_PROGRESS | DONE | FAILED | NOT_APPLICABLE
@Column(name = "status", length = 20, nullable = false)
private String status;

@Column(name = "failure_reason", length = 50)
private String failureReason;

@Column(name = "error_detail", length = 2000)
private String errorDetail;

@Column(name = "nikshay_id", length = 50)
private String nikshayId;

@Column(name = "episode_id", length = 50)
private String episodeId;

@Column(name = "last_attempt_at")
private Timestamp lastAttemptAt;

@Column(name = "is_updated", nullable = false)
private Boolean isUpdated = false;

@CreationTimestamp
@Column(name = "created_at", updatable = false)
private Timestamp createdAt;

@UpdateTimestamp
@Column(name = "updated_at")
private Timestamp updatedAt;
}
Loading