Skip to content

Commit 231c8e6

Browse files
Nandeesh778Piyush7034
authored andcommitted
[INJICERT-1116] Add the Ledger search api. (mosip#368)
* Adding the Ledger search api. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * updated the PR with validation changes. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * Changed the key names in the CredentialLedgerSearchRequest as per specs. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * Resolved the comments by modified the service code and adding the testcases. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * Removed unused dependency and print statements. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * Resolved the comments by modifying the LedgerIssuanceTableCustomRepositoryImpl file. Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> * fix: resolve ApplicationContext failure in RenderingCredentialTemplateRepositoryTest by providing ObjectMapper bean Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> --------- Signed-off-by: Nandeesh <nandeeshv0001@gmail.com> Signed-off-by: Vishwa <visu.vs1@gmail.com> Signed-off-by: Piyush7034 <piyushshukla2100@gmail.com>
1 parent 024fb59 commit 231c8e6

File tree

12 files changed

+276
-6
lines changed

12 files changed

+276
-6
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.mosip.certify.core.dto;
2+
3+
import java.util.Map;
4+
import lombok.Data;
5+
import jakarta.validation.constraints.NotBlank;
6+
7+
@Data
8+
public class CredentialLedgerSearchRequest {
9+
private String credentialId;
10+
11+
@NotBlank(message = "issuerId is mandatory")
12+
private String issuerId;
13+
14+
@NotBlank(message = "credentialType is mandatory")
15+
private String credentialType;
16+
17+
private Map<String, String> indexedAttributesEquals;
18+
}

certify-core/src/main/java/io/mosip/certify/core/dto/CredentialStatusResponse.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import lombok.Data;
44
import java.time.LocalDateTime;
5-
import java.util.List;
6-
import java.util.Map;
75

86
import com.fasterxml.jackson.annotation.JsonFormat;
97

@@ -25,6 +23,4 @@ public class CredentialStatusResponse {
2523

2624
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
2725
private LocalDateTime statusTimestamp;
28-
29-
private List<Map<String, Object>> credentialStatusDetails;
3026
}

certify-core/src/main/java/io/mosip/certify/core/spi/VCIssuanceService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
*/
66
package io.mosip.certify.core.spi;
77

8+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
89
import io.mosip.certify.core.dto.CredentialRequest;
910
import io.mosip.certify.core.dto.CredentialResponse;
1011
import io.mosip.certify.core.dto.CredentialStatusResponse;
1112
import io.mosip.certify.core.dto.UpdateCredentialStatusRequest;
1213

1314
import java.util.Map;
15+
import java.util.List;
1416

1517
public interface VCIssuanceService {
1618

@@ -26,4 +28,6 @@ public interface VCIssuanceService {
2628
Map<String, Object> getDIDDocument();
2729

2830
CredentialStatusResponse updateCredential(UpdateCredentialStatusRequest updateCredentialStatusRequest);
31+
32+
List<CredentialStatusResponse> searchCredentials(CredentialLedgerSearchRequest request);
2933
}

certify-service/src/main/java/io/mosip/certify/controller/VCIssuanceController.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.web.bind.annotation.ResponseStatus;
2323
import org.springframework.web.bind.annotation.RestController;
2424

25+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
2526
import io.mosip.certify.core.dto.CredentialRequest;
2627
import io.mosip.certify.core.dto.CredentialResponse;
2728
import io.mosip.certify.core.dto.UpdateCredentialStatusRequest;
@@ -33,6 +34,8 @@
3334
import jakarta.validation.Valid;
3435
import lombok.extern.slf4j.Slf4j;
3536

37+
import java.util.List;
38+
3639
@Slf4j
3740
@RestController
3841
@RequestMapping("/issuance")
@@ -65,6 +68,16 @@ public ResponseEntity<CredentialStatusResponse> updateCredential(
6568
return ResponseEntity.ok(result);
6669
}
6770

71+
@PostMapping("/ledger-search")
72+
public ResponseEntity<List<CredentialStatusResponse>> searchCredentials(
73+
@Valid @RequestBody CredentialLedgerSearchRequest request) {
74+
List<CredentialStatusResponse> result = vcIssuanceService.searchCredentials(request);
75+
if (result.isEmpty()) {
76+
return ResponseEntity.noContent().build();
77+
}
78+
return ResponseEntity.ok(result);
79+
}
80+
6881
/**
6982
* 1. The credential Endpoint MUST accept Access Tokens
7083
* @param credentialRequest VC credential request
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.mosip.certify.repository;
2+
3+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
4+
import io.mosip.certify.entity.Ledger;
5+
6+
import java.util.List;
7+
8+
public interface LedgerIssuanceTableCustomRepository {
9+
List<Ledger> findBySearchRequest(CredentialLedgerSearchRequest request);
10+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.mosip.certify.repository;
2+
3+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
4+
import io.mosip.certify.entity.Ledger;
5+
6+
import jakarta.persistence.EntityManager;
7+
import jakarta.persistence.PersistenceContext;
8+
import org.springframework.stereotype.Repository;
9+
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
@Repository
17+
public class LedgerIssuanceTableCustomRepositoryImpl implements LedgerIssuanceTableCustomRepository {
18+
19+
@PersistenceContext
20+
private EntityManager entityManager;
21+
22+
private final ObjectMapper objectMapper;
23+
24+
public LedgerIssuanceTableCustomRepositoryImpl(ObjectMapper objectMapper) {
25+
this.objectMapper = objectMapper;
26+
}
27+
28+
@Override
29+
public List<Ledger> findBySearchRequest(CredentialLedgerSearchRequest request) {
30+
try {
31+
StringBuilder sql = new StringBuilder("SELECT * FROM ledger WHERE issuer_id = :issuerId AND credential_type = :credentialType ");
32+
Map<String, Object> params = new HashMap<>();
33+
params.put("issuerId", request.getIssuerId());
34+
params.put("credentialType", request.getCredentialType());
35+
36+
if (request.getCredentialId() != null) {
37+
sql.append(" AND credential_id = :credentialId ");
38+
params.put("credentialId", request.getCredentialId());
39+
}
40+
41+
if (request.getIndexedAttributesEquals() != null && !request.getIndexedAttributesEquals().isEmpty()) {
42+
int i = 0;
43+
for (Map.Entry<String, String> entry : request.getIndexedAttributesEquals().entrySet()) {
44+
String key = entry.getKey();
45+
String value = entry.getValue();
46+
if (key == null || key.isBlank() || value == null || value.isBlank()) continue;
47+
48+
String paramName = "indexedAttr" + i;
49+
sql.append(" AND indexed_attributes @> cast(:" + paramName + " AS jsonb) ");
50+
params.put(paramName, objectMapper.writeValueAsString(Map.of(key, value)));
51+
i++;
52+
}
53+
}
54+
55+
var query = entityManager.createNativeQuery(sql.toString(), Ledger.class);
56+
57+
for (Map.Entry<String, Object> entry : params.entrySet()) {
58+
query.setParameter(entry.getKey(), entry.getValue());
59+
}
60+
61+
return query.getResultList();
62+
} catch (Exception e) {
63+
throw new RuntimeException("Failed to search LedgerIssuanceTable", e);
64+
}
65+
}
66+
}

certify-service/src/main/java/io/mosip/certify/repository/LedgerRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import java.util.Optional;
1414

1515
@Repository
16-
public interface LedgerRepository extends JpaRepository<Ledger, Long> {
16+
public interface LedgerRepository extends JpaRepository<Ledger, Long>, LedgerIssuanceTableCustomRepository {
1717

1818

1919
/**

certify-service/src/main/java/io/mosip/certify/services/CertifyIssuanceServiceImpl.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.text.ParseException;
99
import java.time.OffsetDateTime;
1010
import java.util.*;
11+
import java.util.stream.Collectors;
1112

1213
import com.fasterxml.jackson.core.type.TypeReference;
1314
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -552,4 +553,76 @@ public CredentialStatusResponse updateCredential(UpdateCredentialStatusRequest r
552553
dto.setStatusTimestamp(savedTransaction.getCreatedDtimes());
553554
return dto;
554555
}
556+
557+
@Override
558+
public List<CredentialStatusResponse> searchCredentials(CredentialLedgerSearchRequest request) {
559+
validateSearchRequest(request);
560+
try {
561+
List<Ledger> records = ledgerRepository.findBySearchRequest(request);
562+
563+
if (records.isEmpty()) {
564+
return Collections.emptyList();
565+
}
566+
567+
return records.stream()
568+
.map(this::mapToSearchResponse)
569+
.collect(Collectors.toList());
570+
571+
} catch (Exception e) {
572+
throw new CertifyException("SEARCH_CREDENTIALS_FAILED");
573+
}
574+
}
575+
576+
private CredentialStatusResponse mapToSearchResponse(Ledger record) {
577+
CredentialStatusResponse response = new CredentialStatusResponse();
578+
response.setCredentialId(record.getCredentialId());
579+
response.setIssuerId(record.getIssuerId());
580+
response.setIssueDate(record.getIssueDate().toLocalDateTime());
581+
response.setExpirationDate(record.getExpirationDate() != null ? record.getExpirationDate().toLocalDateTime() : null);
582+
response.setCredentialType(record.getCredentialType());
583+
List<Map<String, Object>> statusDetails = record.getCredentialStatusDetails();
584+
if (statusDetails != null && !statusDetails.isEmpty()) {
585+
Map<String, Object> latestStatus = statusDetails.get(0);
586+
Object statusListCredentialId = latestStatus.get("status_list_credential_id");
587+
Object statusListIndex = latestStatus.get("status_list_index");
588+
Object statusPurpose = latestStatus.get("status_purpose");
589+
Object createdDtimes = latestStatus.get("cr_dtimes");
590+
591+
if (statusListCredentialId != null) {
592+
response.setStatusListCredentialUrl(statusListCredentialId.toString());
593+
}
594+
595+
if (statusListIndex instanceof Number) {
596+
Long index = ((Number) statusListIndex).longValue();
597+
response.setStatusListIndex(index);
598+
}
599+
600+
if (statusPurpose != null) {
601+
response.setStatusPurpose(statusPurpose.toString());
602+
}
603+
604+
if (createdDtimes instanceof Number) {
605+
long timestampMillis = ((Number) createdDtimes).longValue();
606+
OffsetDateTime createdDateTime = OffsetDateTime.ofInstant(
607+
java.time.Instant.ofEpochMilli(timestampMillis),
608+
java.time.ZoneOffset.UTC
609+
);
610+
response.setStatusTimestamp(createdDateTime.toLocalDateTime());
611+
}
612+
}
613+
return response;
614+
}
615+
616+
private void validateSearchRequest(CredentialLedgerSearchRequest request) {
617+
Map<String, String> indexedAttrs = request.getIndexedAttributesEquals();
618+
619+
boolean hasValid = indexedAttrs != null && !indexedAttrs.isEmpty() &&
620+
indexedAttrs.entrySet().stream()
621+
.anyMatch(e -> e.getKey() != null && !e.getKey().isBlank()
622+
&& e.getValue() != null && !e.getValue().isBlank());
623+
624+
if (!hasValid) {
625+
throw new CertifyException("INVALID_SEARCH_CRITERIA");
626+
}
627+
}
555628
}

certify-service/src/main/java/io/mosip/certify/services/VCIssuanceServiceImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.mosip.certify.api.util.Action;
1616
import io.mosip.certify.api.util.ActionStatus;
1717
import io.mosip.certify.core.constants.VCFormats;
18+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
1819
import io.mosip.certify.core.dto.CredentialMetadata;
1920
import io.mosip.certify.core.dto.CredentialRequest;
2021
import io.mosip.certify.core.dto.CredentialResponse;
@@ -201,4 +202,9 @@ public CredentialStatusResponse updateCredential(UpdateCredentialStatusRequest r
201202
throw new CertifyException("This method is not supported in VCIssuanceServiceImpl");
202203
}
203204

205+
@Override
206+
public List<CredentialStatusResponse> searchCredentials(CredentialLedgerSearchRequest request) {
207+
throw new CertifyException("This method is not supported in VCIssuanceServiceImpl");
208+
}
209+
204210
}

certify-service/src/test/java/io/mosip/certify/TestVCIssuanceServiceImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.mosip.certify;
22

33
import io.mosip.certify.core.constants.ErrorConstants;
4+
import io.mosip.certify.core.dto.CredentialLedgerSearchRequest;
45
import io.mosip.certify.core.dto.CredentialRequest;
56
import io.mosip.certify.core.dto.CredentialResponse;
67
import io.mosip.certify.core.dto.CredentialStatusResponse;
@@ -10,6 +11,8 @@
1011
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1112

1213
import java.util.Map;
14+
import java.util.Collections;
15+
import java.util.List;
1316

1417
@ConditionalOnProperty(value = "mosip.certify.plugin-mode", havingValue = "VCIssuance")
1518
public class TestVCIssuanceServiceImpl implements VCIssuanceService {
@@ -36,7 +39,11 @@ public Map<String, Object> getDIDDocument() {
3639

3740
@Override
3841
public CredentialStatusResponse updateCredential(UpdateCredentialStatusRequest request) {
39-
System.out.println("updateCredential called with: " + request);
4042
throw new UnsupportedOperationException("updateCredential is not implemented yet");
4143
}
44+
45+
@Override
46+
public List<CredentialStatusResponse> searchCredentials(CredentialLedgerSearchRequest request) {
47+
return Collections.emptyList();
48+
}
4249
}

0 commit comments

Comments
 (0)