signerSettings = new HashMap<>();
+ signerSettings.put(Constants.APPLICATION_ID, CertifyIssuanceServiceImpl.keyChooser.get(vcSignAlgorithm).getFirst());
+ signerSettings.put(Constants.REFERENCE_ID, CertifyIssuanceServiceImpl.keyChooser.get(vcSignAlgorithm).getLast());
+
+ // Remove existing proof if present before re-signing
+ JSONObject vcDocument = new JSONObject(vcDocumentJson);
+ if (vcDocument.has("proof")) {
+ vcDocument.remove("proof");
+ }
+
+ // Update validFrom timestamp to current time
+ vcDocument.put("validFrom", new Date().toInstant().toString());
+
+ // Sign the updated VC
+ VCResult> vcResult = vcSigner.attachSignature(vcDocument.toString(), signerSettings);
+
+ if (vcResult.getCredential() == null) {
+ log.error("Failed to re-sign status list VC - vcResult.getCredential() returned null");
+ throw new CertifyException("VC_RESIGNATION_FAILED");
+ }
+
+ String resignedVcDocument = vcResult.getCredential().toString();
+ log.debug("Successfully re-signed status list credential");
+
+ return resignedVcDocument;
+
+ } catch (Exception e) {
+ log.error("Error re-signing status list credential", e);
+ throw new CertifyException("VC_RESIGNATION_FAILED");
+ }
+ }
+}
\ No newline at end of file
diff --git a/certify-service/src/main/java/io/mosip/certify/services/StatusListIndexProvider.java b/certify-service/src/main/java/io/mosip/certify/services/StatusListIndexProvider.java
new file mode 100644
index 000000000..340b5d622
--- /dev/null
+++ b/certify-service/src/main/java/io/mosip/certify/services/StatusListIndexProvider.java
@@ -0,0 +1,32 @@
+package io.mosip.certify.services;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Interface for providing status list index assignment strategies
+ */
+public interface StatusListIndexProvider {
+
+ /**
+ * @return A descriptive name or identifier for this index provider strategy
+ * (e.g., "DatabaseRandomAvailableIndexProvider", "RedisSequentialIndexProvider").
+ */
+ String getProviderName();
+
+ /**
+ * Attempts to acquire an available index from the specified status list.
+ *
+ * The implementing class is responsible for ensuring the returned index is unique
+ * for the given listId at the time of acquisition and that it respects
+ * the list's capacity and any applicable usage policies (like usableCapacity).
+ *
+ * @param listId The unique identifier of the status list from which to acquire an index.
+ * @param options A map of optional parameters that might influence the index acquisition.
+ * This allows for flexibility in implementations. Examples:
+ * - "preferredIndex": (Long) a hint for a desired index, if supported.
+ * - "purpose": (String) context for why the index is needed, could influence choice.
+ */
+ Optional acquireIndex(String listId, Map options);
+
+}
\ No newline at end of file
diff --git a/certify-service/src/main/java/io/mosip/certify/services/StatusListUpdateBatchJob.java b/certify-service/src/main/java/io/mosip/certify/services/StatusListUpdateBatchJob.java
new file mode 100644
index 000000000..d07acf5ad
--- /dev/null
+++ b/certify-service/src/main/java/io/mosip/certify/services/StatusListUpdateBatchJob.java
@@ -0,0 +1,294 @@
+package io.mosip.certify.services;
+
+import io.mosip.certify.core.exception.CertifyException;
+import io.mosip.certify.entity.CredentialStatusTransaction;
+import io.mosip.certify.entity.StatusListCredential;
+import io.mosip.certify.repository.CredentialStatusTransactionRepository;
+import io.mosip.certify.repository.StatusListCredentialRepository;
+import io.mosip.certify.utils.BitStringStatusListUtils;
+import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.core.LockAssert;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Batch job service for updating Status List Credentials
+ * Runs hourly to process new credential status transactions and update status lists
+ */
+@Slf4j
+@Service
+public class StatusListUpdateBatchJob {
+
+ @Autowired
+ private CredentialStatusTransactionRepository transactionRepository;
+
+ @Autowired
+ private StatusListCredentialRepository statusListRepository;
+
+ @Autowired
+ private StatusListCredentialService statusListCredentialService;
+
+ @Value("${mosip.certify.batch.status-list-update.enabled:true}")
+ private boolean batchJobEnabled;
+
+ @Value("${mosip.certify.batch.status-list-update.batch-size:1000}")
+ private int batchSize;
+
+ @Value("${mosip.certify.batch.status-list-update.since-time:1970-01-01T00:00:00}")
+ private String sinceTime;
+
+ // Track last processed timestamp to avoid reprocessing
+ private LocalDateTime lastProcessedTime = null;
+
+ /**
+ * Scheduled method that runs every hour to update status lists
+ * Uses @Scheduled with fixedRate to run every hour (3600000 ms)
+ */
+ @Scheduled(cron = "${mosip.certify.batch.status-list-update.cron-expression:0 0 * * * *}")
+ @SchedulerLock(
+ name = "updateStatusLists",
+ lockAtMostFor = "${mosip.certify.batch.status-list-update.lock-at-most-for:50m}",
+ lockAtLeastFor = "${mosip.certify.batch.status-list-update.lock-at-least-for:5m}"
+ )
+ @Transactional
+ public void updateStatusLists() {
+ LockAssert.assertLocked();
+ if (!batchJobEnabled) {
+ log.info("Status list update batch job is disabled");
+ return;
+ }
+
+ log.info("Starting status list update batch job");
+
+ try {
+ // Determine the starting timestamp for processing
+ LocalDateTime startTime = determineStartTime();
+ log.info("Processing transactions since: {}", startTime);
+
+ // Fetch new transactions
+ List newTransactions = fetchNewTransactions(startTime);
+
+ if (newTransactions.isEmpty()) {
+ log.info("No new transactions found since {}", startTime);
+ return;
+ }
+
+ log.info("Found {} new transactions to process", newTransactions.size());
+
+ // Group transactions by status list credential ID
+ Map> transactionsByStatusList = groupTransactionsByStatusList(newTransactions);
+
+ // Update each affected status list
+ int updatedLists = 0;
+ for (Map.Entry> entry : transactionsByStatusList.entrySet()) {
+ String statusListId = entry.getKey();
+ List transactions = entry.getValue();
+
+ try {
+ updateStatusList(statusListId, transactions);
+ updatedLists++;
+ log.info("Successfully updated status list: {}", statusListId);
+ } catch (Exception e) {
+ log.error("Failed to update status list: {}", statusListId, e);
+ // Continue processing other status lists even if one fails
+ }
+ }
+
+ // Update last processed time
+ lastProcessedTime = newTransactions.stream()
+ .map(CredentialStatusTransaction::getCreatedDtimes)
+ .max(LocalDateTime::compareTo)
+ .orElse(LocalDateTime.now());
+
+ log.info("Status list update batch job completed successfully. Updated {} status lists", updatedLists);
+
+ } catch (Exception e) {
+ log.error("Error in status list update batch job", e);
+ throw new CertifyException("BATCH_JOB_EXECUTION_FAILED");
+ }
+ }
+
+ /**
+ * Determine the starting timestamp for processing transactions
+ */
+ private LocalDateTime determineStartTime() {
+ if (lastProcessedTime != null) {
+ return lastProcessedTime;
+ }
+
+ // First run - get the latest update time from existing status lists
+ Optional lastKnownUpdate = statusListRepository.findMaxUpdatedTime();
+
+ if (lastKnownUpdate.isPresent()) {
+ log.info("Using last known status list update time: {}", lastKnownUpdate.get());
+ return lastKnownUpdate.get();
+ }
+
+ // No previous updates found, using configured since time
+ try {
+ LocalDateTime defaultStart = LocalDateTime.parse(sinceTime);
+ log.info("No previous update time found, using configured default start time: {}", defaultStart);
+ return defaultStart;
+ } catch (DateTimeParseException e) {
+ // Fallback: safe default to 24 hours ago if parsing fails
+ LocalDateTime fallbackStart = LocalDateTime.now().minusHours(24);
+ log.warn("Failed to parse configured since-time '{}'. Falling back to 24 hours ago: {}", sinceTime, fallbackStart);
+ return fallbackStart;
+ }
+ }
+
+ /**
+ * Fetch new transactions since the given timestamp
+ */
+ private List fetchNewTransactions(LocalDateTime since) {
+ try {
+ return transactionRepository.findTransactionsSince(since, batchSize);
+ } catch (Exception e) {
+ log.error("Error fetching new transactions since {}", since, e);
+ throw new CertifyException("TRANSACTION_FETCH_FAILED");
+ }
+ }
+
+ /**
+ * Group transactions by their status list credential ID
+ */
+ private Map> groupTransactionsByStatusList(List transactions) {
+
+ return transactions.stream()
+ .filter(t -> t.getStatusListCredentialId() != null)
+ .collect(Collectors.groupingBy(CredentialStatusTransaction::getStatusListCredentialId));
+ }
+
+ /**
+ * Update a specific status list with the given transactions
+ */
+ @Transactional
+ public void updateStatusList(String statusListId, List transactions) {
+ log.info("Updating status list {} with {} transactions", statusListId, transactions.size());
+
+ try {
+ // Fetch the current status list credential
+ Optional optionalStatusList = statusListRepository.findById(statusListId);
+
+ if (optionalStatusList.isEmpty()) {
+ log.error("Status list credential not found: {}", statusListId);
+ throw new CertifyException("STATUS_LIST_NOT_FOUND");
+ }
+
+ StatusListCredential statusListCredential = optionalStatusList.get();
+
+ // Get current status data for this status list
+ Map currentStatuses = getCurrentStatusData(statusListId);
+
+ // Apply transaction updates to the status data
+ Map updatedStatuses = applyTransactionUpdates(currentStatuses, transactions);
+
+ // Generate new encoded list
+ String newEncodedList = BitStringStatusListUtils.generateEncodedList(updatedStatuses, statusListCredential.getCapacity());
+
+ // Update the status list credential with new encoded list
+ updateStatusListCredential(statusListCredential, newEncodedList);
+
+ log.info("Successfully updated status list credential: {}", statusListId);
+
+ } catch (Exception e) {
+ log.error("Error updating status list: {}", statusListId, e);
+ throw new CertifyException("STATUS_LIST_UPDATE_FAILED");
+ }
+ }
+
+ /**
+ * Get current status data for a specific status list from transactions
+ */
+ private Map getCurrentStatusData(String statusListId) {
+ // Get the latest status for each index in this status list
+ List latestTransactions =
+ transactionRepository.findLatestStatusByStatusListId(statusListId);
+
+ Map statusMap = new HashMap<>();
+ for (CredentialStatusTransaction transaction : latestTransactions) {
+ if (transaction.getStatusListIndex() != null) {
+ statusMap.put(transaction.getStatusListIndex(), transaction.getStatusValue());
+ }
+ }
+
+ return statusMap;
+ }
+
+ /**
+ * Apply transaction updates to the current status data
+ */
+ private Map applyTransactionUpdates(
+ Map currentStatuses,
+ List transactions) {
+
+ Map updatedStatuses = new HashMap<>(currentStatuses);
+
+ // Sort transactions by timestamp to apply them in chronological order
+ transactions.sort(Comparator.comparing(CredentialStatusTransaction::getCreatedDtimes));
+
+ for (CredentialStatusTransaction transaction : transactions) {
+ if (transaction.getStatusListIndex() != null) {
+ updatedStatuses.put(transaction.getStatusListIndex(), transaction.getStatusValue());
+ log.info("Applied transaction: index={}, value={}, timestamp={}",
+ transaction.getStatusListIndex(),
+ transaction.getStatusValue(),
+ transaction.getCreatedDtimes());
+ }
+ }
+
+ return updatedStatuses;
+ }
+
+ /**
+ * Update the status list credential with the new encoded list
+ */
+ @Transactional
+ public void updateStatusListCredential(StatusListCredential statusListCredential, String newEncodedList) {
+ try {
+ log.info("Starting update of StatusListCredential with ID: {}", statusListCredential.getId());
+
+ // Parse the current VC document
+ JSONObject vcDocument = new JSONObject(statusListCredential.getVcDocument());
+ log.info("Parsed VC document for StatusListCredential ID: {}", statusListCredential.getId());
+
+ // Update the encodedList in the credential subject
+ JSONObject credentialSubject = vcDocument.getJSONObject("credentialSubject");
+ credentialSubject.put("encodedList", newEncodedList);
+ log.info("Updated encodedList for StatusListCredential ID: {}", newEncodedList);
+
+ // Update timestamps
+ String newValidFrom = new Date().toInstant().toString();
+ vcDocument.put("validFrom", newValidFrom);
+ log.info("Set new validFrom timestamp: {} for StatusListCredential ID: {}", newValidFrom, statusListCredential.getId());
+
+ // Re-sign the status list credential
+ String updatedVcDocument = statusListCredentialService.resignStatusListCredential(vcDocument.toString());
+ log.info("Re-signed VC document for StatusListCredential ID: {}", statusListCredential.getId());
+
+ // Update the database record
+ statusListCredential.setVcDocument(updatedVcDocument);
+ statusListCredential.setUpdatedDtimes(LocalDateTime.now());
+ statusListRepository.save(statusListCredential);
+
+ log.info("Successfully updated and saved StatusListCredential ID: {}", statusListCredential.getId());
+
+ } catch (Exception e) {
+ log.error("Error updating StatusListCredential ID: {}", statusListCredential.getId(), e);
+ throw new CertifyException("STATUS_LIST_CREDENTIAL_UPDATE_FAILED");
+ }
+ }
+}
+
+//H4sIAAAAAAAA_-3OMQ0AAAgDsEnAv1o87CEhrYImAHTmOgAAAAAAAAAAAAAAAAAAwCML9WKdBQBAAAA
+//H4sIAAAAAAAA_-3OMQkAAAgAMCMY2ehiBRF8tgSLAGAnvwMAAAAAAAAAAAAAwI36DgAAAMBoH-LL9QBAAAA
\ No newline at end of file
diff --git a/certify-service/src/main/java/io/mosip/certify/utils/BitStringStatusListUtils.java b/certify-service/src/main/java/io/mosip/certify/utils/BitStringStatusListUtils.java
new file mode 100644
index 000000000..743200f01
--- /dev/null
+++ b/certify-service/src/main/java/io/mosip/certify/utils/BitStringStatusListUtils.java
@@ -0,0 +1,138 @@
+package io.mosip.certify.utils;
+
+import io.mosip.certify.core.exception.CertifyException;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Utility class to handle bit string operations for status lists
+ * This utility provides static methods for manipulating encoded status lists
+ */
+@Slf4j
+public final class BitStringStatusListUtils {
+
+ // Private constructor to prevent instantiation
+ private BitStringStatusListUtils() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+
+ /**
+ * Generate encoded list from a map of index-status pairs
+ *
+ * @param statusMap Map containing index -> status mappings
+ * @param capacity Total capacity of the status list
+ * @return Base64URL encoded compressed bitstring
+ */
+ public static String generateEncodedList(Map statusMap, long capacity) {
+ log.info("Generating encoded list from status map with {} entries for capacity {}",
+ statusMap.size(), capacity);
+
+ try {
+ // Create bitstring array initialized to false (0)
+ boolean[] bitstring = new boolean[(int) capacity];
+
+ // Set the appropriate bits based on the status map
+ for (Map.Entry entry : statusMap.entrySet()) {
+ long index = entry.getKey();
+ boolean status = entry.getValue();
+
+ if (index >= 0 && index < capacity) {
+ bitstring[(int) index] = status;
+ } else {
+ log.warn("Index {} is out of bounds for capacity {}", index, capacity);
+ }
+ }
+
+ // Convert bitstring to byte array
+ byte[] byteArray = convertBitstringToByteArray(bitstring);
+
+ // Compress the byte array
+ byte[] compressedBytes = compressByteArray(byteArray);
+
+ // Encode to base64url
+ String encodedList = Base64.getUrlEncoder().withoutPadding().encodeToString(compressedBytes);
+
+ log.info("Generated encoded list of length {} from {} status entries",
+ encodedList.length(), statusMap.size());
+
+ return encodedList;
+
+ } catch (Exception e) {
+ log.error("Error generating encoded list from status map", e);
+ throw new CertifyException("ENCODED_LIST_GENERATION_FAILED");
+ }
+ }
+
+ /**
+ * Creates an empty encoded list (all bits set to 0) according to W3C Bitstring Status List v1.0
+ *
+ * @param capacity the number of bits in the list
+ * @return Multibase-encoded base64url (with no padding) string representing the GZIP-compressed bit array
+ * @throws RuntimeException if compression fails
+ */
+ public static String createEmptyEncodedList(long capacity) {
+ log.debug("Creating empty encoded list with capacity {}", capacity);
+
+ // Ensure minimum size of 16KB (131,072 bits) as per specification
+ long actualCapacity = Math.max(capacity, 131072L);
+
+ int numBytes = (int) Math.ceil(actualCapacity / 8.0);
+ byte[] emptyBitstring = new byte[numBytes];
+
+ try {
+ // GZIP compress the bitstring as required by the specification
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
+ gzipOut.write(emptyBitstring);
+ }
+ byte[] compressedBitstring = baos.toByteArray();
+
+ // Multibase-encode using base64url (with no padding) as required by specification
+ String base64urlEncoded = Base64.getUrlEncoder().withoutPadding()
+ .encodeToString(compressedBitstring);
+
+ return "u" + base64urlEncoded;
+
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to compress bitstring", e);
+ }
+ }
+
+ /**
+ * Convert bitstring boolean array to byte array
+ * Each byte contains 8 bits
+ */
+ private static byte[] convertBitstringToByteArray(boolean[] bitstring) {
+ int byteLength = (bitstring.length + 7) / 8; // Round up to nearest byte
+ byte[] byteArray = new byte[byteLength];
+
+ for (int i = 0; i < bitstring.length; i++) {
+ if (bitstring[i]) {
+ int byteIndex = i / 8;
+ int bitIndex = i % 8;
+ byteArray[byteIndex] |= (1 << (7 - bitIndex)); // Set bit (MSB first)
+ }
+ }
+
+ return byteArray;
+ }
+
+ /**
+ * Compress byte array using GZIP compression
+ */
+ private static byte[] compressByteArray(byte[] input) throws IOException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
+
+ gzipOut.write(input);
+ gzipOut.finish();
+
+ return baos.toByteArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/certify-service/src/main/java/io/mosip/certify/vcformatters/VelocityTemplatingEngineImpl.java b/certify-service/src/main/java/io/mosip/certify/vcformatters/VelocityTemplatingEngineImpl.java
index 1546fd66a..3e256d348 100644
--- a/certify-service/src/main/java/io/mosip/certify/vcformatters/VelocityTemplatingEngineImpl.java
+++ b/certify-service/src/main/java/io/mosip/certify/vcformatters/VelocityTemplatingEngineImpl.java
@@ -262,7 +262,10 @@ protected static Map jsonify(Map valueMap) {
} else if (value instanceof String){
// entities which need to be quoted
finalTemplate.put(key, JSONObject.wrap(value));
- } else {
+ } else if( value instanceof Map,?>) {
+ finalTemplate.put(key,JSONObject.wrap(value));
+ }
+ else {
// no conversion needed
finalTemplate.put(key, value);
}
diff --git a/certify-service/src/main/resources/application-local.properties b/certify-service/src/main/resources/application-local.properties
index eaace08df..8c1d830ee 100644
--- a/certify-service/src/main/resources/application-local.properties
+++ b/certify-service/src/main/resources/application-local.properties
@@ -7,10 +7,10 @@ mosip.certify.security.auth.get-urls={}
mosip.certify.security.ignore-csrf-urls=**/actuator/**,/favicon.ico,**/error,\
**/swagger-ui/**,**/v3/api-docs/**,\
- **/issuance/**,**/system-info/**,**/credentials/**
+ **/issuance/**,**/system-info/**,**/credentials/**,**/status-list/**
mosip.certify.security.ignore-auth-urls=/actuator/**,**/error,**/swagger-ui/**,\
- **/v3/api-docs/**, **/issuance/**,/system-info/**,/rendering-template/**,/credentials/**
+ **/v3/api-docs/**, **/issuance/**,/system-info/**,/rendering-template/**,/credentials/**,**/status-list/**
## ------------------------------------------ Discovery openid-configuration -------------------------------------------
@@ -20,7 +20,7 @@ mosip.certify.authorization.url=http://localhost:8088
mosip.certify.discovery.issuer-id=${mosip.certify.domain.url}${server.servlet.path}
mosip.certify.data-provider-plugin.issuer.vc-sign-algo=Ed25519Signature2020
mosip.certify.plugin-mode=DataProvider
-mosip.certify.data-provider-plugin.data-integrity.crypto-suite=ecdsa-rdfc-2019
+#mosip.certify.data-provider-plugin.data-integrity.crypto-suite=ecdsa-rdfc-2019
##--------------change this later---------------------------------
mosip.certify.supported.jwt-proof-alg={'RS256','PS256','ES256'}
@@ -354,7 +354,8 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/inji_certify?currentSchem
spring.datasource.username=postgres
spring.datasource.password=postgres
-spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
diff --git a/db_scripts/inji_certify/ddl/certify-credential_status_transaction.sql b/db_scripts/inji_certify/ddl/certify-credential_status_transaction.sql
new file mode 100644
index 000000000..fdffc9b30
--- /dev/null
+++ b/db_scripts/inji_certify/ddl/certify-credential_status_transaction.sql
@@ -0,0 +1,57 @@
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
+-- -------------------------------------------------------------------------------------------------
+-- Database Name: inji_certify
+-- Table Name : credential_status_transaction
+-- Purpose : Credential Status Transaction Table
+--
+--
+-- Modified Date Modified By Comments / Remarks
+-- ------------------------------------------------------------------------------------------
+-- ------------------------------------------------------------------------------------------
+
+-- Create credential_status_transaction table
+CREATE TABLE IF NOT EXISTS credential_status_transaction (
+ transaction_log_id SERIAL PRIMARY KEY, -- Unique ID for this transaction log entry
+ credential_id VARCHAR(255) NOT NULL, -- The ID of the credential this transaction pertains to (should exist in ledger.credential_id)
+ status_purpose VARCHAR(100), -- The purpose of this status update
+ status_value boolean, -- The status value (true/false)
+ status_list_credential_id VARCHAR(255), -- The ID of the status list credential involved, if any
+ status_list_index BIGINT, -- The index on the status list, if any
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp
+ upd_dtimes TIMESTAMP, -- Update timestamp
+
+ -- Foreign key constraint to ledger table
+ CONSTRAINT fk_credential_status_transaction_ledger
+ FOREIGN KEY(credential_id)
+ REFERENCES ledger(credential_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+
+ -- Foreign key constraint to status_list_credential table
+ CONSTRAINT fk_credential_status_transaction_status_list
+ FOREIGN KEY(status_list_credential_id)
+ REFERENCES status_list_credential(id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE credential_status_transaction IS 'Transaction log for credential status changes and updates.';
+COMMENT ON COLUMN credential_status_transaction.transaction_log_id IS 'Serial primary key for the transaction log entry.';
+COMMENT ON COLUMN credential_status_transaction.credential_id IS 'The ID of the credential this transaction pertains to (references ledger.credential_id).';
+COMMENT ON COLUMN credential_status_transaction.status_purpose IS 'The purpose of this status update (e.g., revocation, suspension).';
+COMMENT ON COLUMN credential_status_transaction.status_value IS 'The status value (true for revoked/suspended, false for active).';
+COMMENT ON COLUMN credential_status_transaction.status_list_credential_id IS 'The ID of the status list credential involved, if any.';
+COMMENT ON COLUMN credential_status_transaction.status_list_index IS 'The index on the status list, if any.';
+COMMENT ON COLUMN credential_status_transaction.cr_dtimes IS 'Timestamp when this transaction was created.';
+COMMENT ON COLUMN credential_status_transaction.upd_dtimes IS 'Timestamp when this transaction was last updated.';
+
+-- Create indexes for credential_status_transaction
+CREATE INDEX IF NOT EXISTS idx_cst_credential_id ON credential_status_transaction(credential_id);
+CREATE INDEX IF NOT EXISTS idx_cst_status_purpose ON credential_status_transaction(status_purpose);
+CREATE INDEX IF NOT EXISTS idx_cst_status_list_credential_id ON credential_status_transaction(status_list_credential_id);
+CREATE INDEX IF NOT EXISTS idx_cst_status_list_index ON credential_status_transaction(status_list_index);
+CREATE INDEX IF NOT EXISTS idx_cst_cr_dtimes ON credential_status_transaction(cr_dtimes);
+CREATE INDEX IF NOT EXISTS idx_cst_status_value ON credential_status_transaction(status_value);
\ No newline at end of file
diff --git a/db_scripts/inji_certify/ddl/certify-ledger.sql b/db_scripts/inji_certify/ddl/certify-ledger.sql
new file mode 100644
index 000000000..3bce1240a
--- /dev/null
+++ b/db_scripts/inji_certify/ddl/certify-ledger.sql
@@ -0,0 +1,50 @@
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
+-- -------------------------------------------------------------------------------------------------
+-- Database Name: inji_certify
+-- Table Name : ledger
+-- Purpose : Ledger to store status list credential entries
+--
+--
+-- Modified Date Modified By Comments / Remarks
+-- ------------------------------------------------------------------------------------------
+-- ------------------------------------------------------------------------------------------
+-- Create ledger table (insert only table, data once added will not be updated)
+CREATE TABLE ledger (
+ id SERIAL PRIMARY KEY, -- Auto-incrementing serial primary key
+ credential_id VARCHAR(255) NOT NULL, -- Unique ID of the Verifiable Credential WHOSE STATUS IS BEING TRACKED
+ issuer_id VARCHAR(255) NOT NULL, -- Issuer of the TRACKED credential
+ issue_date TIMESTAMPTZ NOT NULL, -- Issuance date of the TRACKED credential
+ expiration_date TIMESTAMPTZ, -- Expiration date of the TRACKED credential, if any
+ credential_type VARCHAR(100) NOT NULL, -- Type of the TRACKED credential (e.g., 'VerifiableId')
+ indexed_attributes JSONB, -- Optional searchable attributes from the TRACKED credential
+ credential_status_details JSONB NOT NULL DEFAULT '[]'::jsonb, -- Stores a list of status objects for this credential, defaults to an empty array.
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp of this ledger entry for the tracked credential
+
+ -- Constraints
+ CONSTRAINT uq_ledger_tracked_credential_id UNIQUE (credential_id), -- Ensure tracked credential_id is unique
+ CONSTRAINT ensure_credential_status_details_is_array CHECK (jsonb_typeof(credential_status_details) = 'array') -- Ensure it's always a JSON array
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE ledger IS 'Stores intrinsic information about tracked Verifiable Credentials and their status history.';
+COMMENT ON COLUMN ledger.id IS 'Serial primary key for the ledger table.';
+COMMENT ON COLUMN ledger.credential_id IS 'Unique identifier of the Verifiable Credential whose status is being tracked. Must be unique across the table.';
+COMMENT ON COLUMN ledger.issuer_id IS 'Identifier of the issuer of the tracked credential.';
+COMMENT ON COLUMN ledger.issue_date IS 'Issuance date of the tracked credential.';
+COMMENT ON COLUMN ledger.expiration_date IS 'Expiration date of the tracked credential, if applicable.';
+COMMENT ON COLUMN ledger.credential_type IS 'The type(s) of the tracked credential (e.g., VerifiableId, ProofOfEnrollment).';
+COMMENT ON COLUMN ledger.indexed_attributes IS 'Stores specific attributes extracted from the tracked credential for optimized searching.';
+COMMENT ON COLUMN ledger.credential_status_details IS 'An array of status objects, guaranteed to be a JSON array (list). Defaults to an empty list []. Each object can contain: status_purpose, status_value (boolean), status_list_credential_id, status_list_index, cr_dtimes, upd_dtimes.';
+COMMENT ON COLUMN ledger.cr_dtimes IS 'Timestamp of when this ledger record for the tracked credential was created.';
+
+-- Create indexes for ledger
+CREATE INDEX IF NOT EXISTS idx_ledger_credential_id ON ledger(credential_id);
+CREATE INDEX IF NOT EXISTS idx_ledger_issuer_id ON ledger(issuer_id);
+CREATE INDEX IF NOT EXISTS idx_ledger_credential_type ON ledger(credential_type);
+CREATE INDEX IF NOT EXISTS idx_ledger_issue_date ON ledger(issue_date);
+CREATE INDEX IF NOT EXISTS idx_ledger_expiration_date ON ledger(expiration_date);
+CREATE INDEX IF NOT EXISTS idx_ledger_cr_dtimes ON ledger(cr_dtimes);
+CREATE INDEX IF NOT EXISTS idx_gin_ledger_indexed_attrs ON ledger USING GIN (indexed_attributes);
+CREATE INDEX IF NOT EXISTS idx_gin_ledger_status_details ON ledger USING GIN (credential_status_details);
\ No newline at end of file
diff --git a/db_scripts/inji_certify/ddl/certify-status_list_available_indices.sql b/db_scripts/inji_certify/ddl/certify-status_list_available_indices.sql
new file mode 100644
index 000000000..756f6bac3
--- /dev/null
+++ b/db_scripts/inji_certify/ddl/certify-status_list_available_indices.sql
@@ -0,0 +1,53 @@
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
+-- -------------------------------------------------------------------------------------------------
+-- Database Name: inji_certify
+-- Table Name : status_list_available_indices
+-- Purpose : status_list_available_indices to store status list available indices
+--
+--
+-- Modified Date Modified By Comments / Remarks
+-- ------------------------------------------------------------------------------------------
+-- ------------------------------------------------------------------------------------------
+-- Create status_list_available_indices table
+CREATE TABLE status_list_available_indices (
+ id SERIAL PRIMARY KEY, -- Serial primary key
+ status_list_credential_id VARCHAR(255) NOT NULL, -- References status_list_credential.id
+ list_index BIGINT NOT NULL, -- The numerical index within the status list
+ is_assigned BOOLEAN NOT NULL DEFAULT FALSE, -- Flag indicating if this index has been assigned
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp
+ upd_dtimes TIMESTAMP, -- Update timestamp
+
+ -- Foreign key constraint
+ CONSTRAINT fk_status_list_credential
+ FOREIGN KEY(status_list_credential_id)
+ REFERENCES status_list_credential(id)
+ ON DELETE CASCADE -- If a status list credential is deleted, its available index entries are also deleted.
+ ON UPDATE CASCADE, -- If the ID of a status list credential changes, update it here too.
+
+ -- Unique constraint to ensure each index within a list is represented only once
+ CONSTRAINT uq_list_id_and_index
+ UNIQUE (status_list_credential_id, list_index)
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE status_list_available_indices IS 'Helper table to manage and assign available indices from status list credentials.';
+COMMENT ON COLUMN status_list_available_indices.id IS 'Serial primary key for the available index entry.';
+COMMENT ON COLUMN status_list_available_indices.status_list_credential_id IS 'Identifier of the status list credential this index belongs to (FK to status_list_credential.id).';
+COMMENT ON COLUMN status_list_available_indices.list_index IS 'The numerical index (e.g., 0 to N-1) within the specified status list.';
+COMMENT ON COLUMN status_list_available_indices.is_assigned IS 'Flag indicating if this specific index has been assigned (TRUE) or is available (FALSE).';
+COMMENT ON COLUMN status_list_available_indices.cr_dtimes IS 'Timestamp when this index entry record was created (typically when the parent status list was populated).';
+COMMENT ON COLUMN status_list_available_indices.upd_dtimes IS 'Timestamp when this index entry record was last updated (e.g., when is_assigned changed).';
+
+-- Create indexes for status_list_available_indices
+-- Partial index specifically for finding available slots
+CREATE INDEX IF NOT EXISTS idx_sla_available_indices
+ ON status_list_available_indices (status_list_credential_id, is_assigned, list_index)
+ WHERE is_assigned = FALSE;
+
+-- Additional indexes for performance
+CREATE INDEX IF NOT EXISTS idx_sla_status_list_credential_id ON status_list_available_indices(status_list_credential_id);
+CREATE INDEX IF NOT EXISTS idx_sla_is_assigned ON status_list_available_indices(is_assigned);
+CREATE INDEX IF NOT EXISTS idx_sla_list_index ON status_list_available_indices(list_index);
+CREATE INDEX IF NOT EXISTS idx_sla_cr_dtimes ON status_list_available_indices(cr_dtimes);
\ No newline at end of file
diff --git a/db_scripts/inji_certify/ddl/certify-status_list_credential.sql b/db_scripts/inji_certify/ddl/certify-status_list_credential.sql
new file mode 100644
index 000000000..70533c267
--- /dev/null
+++ b/db_scripts/inji_certify/ddl/certify-status_list_credential.sql
@@ -0,0 +1,41 @@
+-- This Source Code Form is subject to the terms of the Mozilla Public
+-- License, v. 2.0. If a copy of the MPL was not distributed with this
+-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
+-- -------------------------------------------------------------------------------------------------
+-- Database Name: inji_certify
+-- Table Name : status_list_credential
+-- Purpose : status_list_credential to store status list credential
+--
+--
+-- Modified Date Modified By Comments / Remarks
+-- ------------------------------------------------------------------------------------------
+-- ------------------------------------------------------------------------------------------
+-- Create ENUM type for credential status
+CREATE TYPE credential_status_enum AS ENUM ('available', 'full');
+
+-- Create status_list_credential table
+CREATE TABLE status_list_credential (
+ id VARCHAR(255) PRIMARY KEY, -- The unique ID (URL/DID/URN) extracted from the VC's 'id' field.
+ vc_document bytea NOT NULL, -- Stores the entire Verifiable Credential JSON document.
+ credential_type VARCHAR(100) NOT NULL, -- Type of the status list (e.g., 'StatusList2021Credential')
+ status_purpose VARCHAR(100), -- Intended purpose of this list within the system (e.g., 'revocation', 'suspension', 'general'). NULLABLE.
+ capacity BIGINT, --- length of status list
+ credential_status credential_status_enum, -- Use the created ENUM type here
+ cr_dtimes timestamp NOT NULL default now(),
+ upd_dtimes timestamp -- When this VC record was last updated in the system
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE status_list_credential IS 'Stores full Status List Verifiable Credentials, including their type and intended purpose within the system.';
+COMMENT ON COLUMN status_list_credential.id IS 'Unique identifier (URL/DID/URN) of the Status List VC (extracted from vc_document.id). Primary Key.';
+COMMENT ON COLUMN status_list_credential.vc_document IS 'The complete JSON document of the Status List Verifiable Credential.';
+COMMENT ON COLUMN status_list_credential.credential_type IS 'The type of the Status List credential, often found in vc_document.type (e.g., StatusList2021Credential).';
+COMMENT ON COLUMN status_list_credential.status_purpose IS 'The intended purpose assigned to this entire Status List within the system (e.g., revocation, suspension, general). This may be based on convention or system policy, distinct from the credentialStatus.statusPurpose used by individual credentials.';
+COMMENT ON COLUMN status_list_credential.cr_dtimes IS 'Timestamp when this Status List VC was first added/fetched into the local system.';
+COMMENT ON COLUMN status_list_credential.upd_dtimes IS 'Timestamp when this Status List VC record was last updated.';
+
+-- Create indexes
+CREATE INDEX IF NOT EXISTS idx_slc_status_purpose ON status_list_credential(status_purpose);
+CREATE INDEX IF NOT EXISTS idx_slc_credential_type ON status_list_credential(credential_type);
+CREATE INDEX IF NOT EXISTS idx_slc_credential_status ON status_list_credential(credential_status);
+CREATE INDEX IF NOT EXISTS idx_slc_cr_dtimes ON status_list_credential(cr_dtimes);
\ No newline at end of file
diff --git a/db_scripts/inji_certify/ddl/combined.sql b/db_scripts/inji_certify/ddl/combined.sql
deleted file mode 100644
index b34deb4e8..000000000
--- a/db_scripts/inji_certify/ddl/combined.sql
+++ /dev/null
@@ -1,223 +0,0 @@
-DROP TABLE IF EXISTS key_alias CASCADE CONSTRAINTS;
-CREATE TABLE IF NOT EXISTS key_alias(
- id character varying(36) NOT NULL,
- app_id character varying(36) NOT NULL,
- ref_id character varying(128),
- key_gen_dtimes timestamp,
- key_expire_dtimes timestamp,
- status_code character varying(36),
- lang_code character varying(3),
- cr_by character varying(256) NOT NULL,
- cr_dtimes timestamp NOT NULL,
- upd_by character varying(256),
- upd_dtimes timestamp,
- is_deleted boolean DEFAULT FALSE,
- del_dtimes timestamp,
- cert_thumbprint character varying(100),
- uni_ident character varying(50),
- CONSTRAINT pk_keymals_id PRIMARY KEY (id),
- CONSTRAINT uni_ident_const UNIQUE (uni_ident)
-);
-DROP TABLE IF EXISTS key_policy_def CASCADE CONSTRAINTS;
-CREATE TABLE IF NOT EXISTS key_policy_def(
- app_id character varying(36) NOT NULL,
- key_validity_duration smallint,
- is_active boolean NOT NULL,
- pre_expire_days smallint,
- access_allowed character varying(1024),
- cr_by character varying(256) NOT NULL,
- cr_dtimes timestamp NOT NULL,
- upd_by character varying(256),
- upd_dtimes timestamp,
- is_deleted boolean DEFAULT FALSE,
- del_dtimes timestamp,
- CONSTRAINT pk_keypdef_id PRIMARY KEY (app_id)
-);
-DROP TABLE IF EXISTS key_store CASCADE CONSTRAINTS;
-CREATE TABLE IF NOT EXISTS key_store(
- id character varying(36) NOT NULL,
- master_key character varying(36) NOT NULL,
- private_key character varying(2500) NOT NULL,
- certificate_data character varying NOT NULL,
- cr_by character varying(256) NOT NULL,
- cr_dtimes timestamp NOT NULL,
- upd_by character varying(256),
- upd_dtimes timestamp,
- is_deleted boolean DEFAULT FALSE,
- del_dtimes timestamp,
- CONSTRAINT pk_keystr_id PRIMARY KEY (id)
-);
-DROP TABLE IF EXISTS rendering_template CASCADE CONSTRAINTS;
-CREATE TABLE IF NOT EXISTS rendering_template (
- id UUID NOT NULL,
- template VARCHAR NOT NULL,
- cr_dtimes timestamp NOT NULL,
- upd_dtimes timestamp,
- CONSTRAINT pk_svgtmp_id PRIMARY KEY (id)
-);
-
-CREATE TABLE credential_config (
- credential_config_key_id VARCHAR(255) NOT NULL UNIQUE,
- config_id VARCHAR(255),
- status VARCHAR(255),
- vc_template VARCHAR,
- doctype VARCHAR,
- vct VARCHAR,
- context VARCHAR NOT NULL,
- credential_type VARCHAR NOT NULL,
- credential_format VARCHAR(255) NOT NULL,
- did_url VARCHAR NOT NULL,
- key_manager_app_id VARCHAR(36) NOT NULL,
- key_manager_ref_id VARCHAR(128),
- signature_algo VARCHAR(36),
- sd_claim VARCHAR,
- display JSONB NOT NULL,
- display_order TEXT[] NOT NULL,
- scope VARCHAR(255) NOT NULL,
- cryptographic_binding_methods_supported TEXT[] NOT NULL,
- credential_signing_alg_values_supported TEXT[] NOT NULL,
- proof_types_supported JSONB NOT NULL,
- credential_subject JSONB,
- claims JSONB,
- plugin_configurations JSONB,
- cr_dtimes TIMESTAMP NOT NULL,
- upd_dtimes TIMESTAMP,
- CONSTRAINT pk_config_id PRIMARY KEY (context, credential_type, credential_format)
-);
-
-
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('ROOT', 2920, 1125, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_SERVICE', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_PARTNER', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_VC_SIGN_RSA', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_VC_SIGN_ED25519', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('BASE', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_VC_SIGN_EC_K1', 1095, 60, 'NA', true, 'mosipadmin', now());
-INSERT INTO key_policy_def(APP_ID,KEY_VALIDITY_DURATION,PRE_EXPIRE_DAYS,ACCESS_ALLOWED,IS_ACTIVE,CR_BY,CR_DTIMES) VALUES('CERTIFY_VC_SIGN_EC_R1', 1095, 60, 'NA', true, 'mosipadmin', now());
-
-
-
-INSERT INTO template_data (context, credential_type, template, credential_format, key_manager_app_id, key_manager_ref_id, did_url, cr_dtimes, upd_dtimes) VALUES ('https://www.w3.org/2018/credentials/v1', 'FarmerCredential,VerifiableCredential', '{
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://piyush7034.github.io/my-files/farmer.json",
- "https://w3id.org/security/suites/ed25519-2020/v1"
- ],
- "issuer": "${_issuer}",
- "type": [
- "VerifiableCredential",
- "FarmerCredential"
- ],
- "issuanceDate": "${validFrom}",
- "expirationDate": "${validUntil}",
- "credentialSubject": {
- "id": "${id}",
- "fullName": "${fullName}",
- "mobileNumber": "${mobileNumber}",
- "dateOfBirth": "${dateOfBirth}",
- "gender": "${gender}",
- "state": "${state}",
- "district": "${district}",
- "villageOrTown": "${villageOrTown}",
- "postalCode": "${postalCode}",
- "landArea": "${landArea}",
- "landOwnershipType": "${landOwnershipType}",
- "primaryCropType": "${primaryCropType}",
- "secondaryCropType": "${secondaryCropType}",
- "face": "${face}",
- "farmerID": "${farmerID}"
- }
-}
-', 'ldp_vc', 'CERTIFY_VC_SIGN_ED25519','ED25519_SIGN','did:web:vharsh.github.io:DID:harsh#key-0', '2024-10-24 12:32:38.065994', NULL);
-
-INSERT INTO template_data(context, credential_type, template, credential_format, key_manager_app_id, key_manager_ref_id, did_url, cr_dtimes, upd_dtimes) VALUES ('https://www.w3.org/ns/credentials/v2', 'FarmerCredential,VerifiableCredential', '{
- "@context": [
- "https://www.w3.org/ns/credentials/v2",
- "https://piyush7034.github.io/my-files/farmer.json",
- "https://w3id.org/security/suites/ed25519-2020/v1"
- ],
- "issuer": "${_issuer}",
- "type": [
- "VerifiableCredential",
- "FarmerCredential"
- ],
- "validFrom": "${validFrom}",
- "validUntil": "${validUntil}",
- "credentialSubject": {
- "id": "${id}",
- "fullName": "${fullName}",
- "mobileNumber": "${mobileNumber}",
- "dateOfBirth": "${dateOfBirth}",
- "gender": "${gender}",
- "state": "${state}",
- "district": "${district}",
- "villageOrTown": "${villageOrTown}",
- "postalCode": "${postalCode}",
- "landArea": "${landArea}",
- "landOwnershipType": "${landOwnershipType}",
- "primaryCropType": "${primaryCropType}",
- "secondaryCropType": "${secondaryCropType}",
- "face": "${face}",
- "farmerID": "${farmerID}"
- }
-}', 'ldp_vc', 'CERTIFY_VC_SIGN_ED25519','ED25519_SIGN', 'did:web:vharsh.github.io:DID:harsh', '2024-10-24 12:32:38.065994', NULL);
-
-
-INSERT INTO template_data (context, credential_type, template, credential_format, key_manager_app_id, key_manager_ref_id, did_url, cr_dtimes, upd_dtimes) VALUES ('https://www.w3.org/2018/credentials/v1', 'FarmerCredential,VerifiableCredential', '{
- "iss": "${_issuer}",
- "iat": ${_iat},
- "nbf": ${_nbf},
- "exp": ${_exp},
- "vc": {
- "@context": ["https://www.w3.org/ns/credentials/v2"],
- "type": ["VerifiableCredential", "AadhaarCredential"],
- "credentialSubject": {
- "id": "${id}",
- "fullName": "${fullName}",
- "mobileNumber": "${mobileNumber}",
- "dateOfBirth": "${dateOfBirth}",
- "gender": "${gender}",
- "state": "${state}",
- "district": "${district}",
- "villageOrTown": "${villageOrTown}",
- "postalCode": "${postalCode}",
- "landArea": "${landArea}",
- "landOwnershipType": "${landOwnershipType}",
- "primaryCropType": "${primaryCropType}",
- "secondaryCropType": "${secondaryCropType}",
- "face": "${face}",
- "farmerID": "${farmerID}"
- }
- }
- }
-', 'vc+sd-jwt', 'CERTIFY_VC_SIGN_EC_R1','EC_SECP256R1_SIGN','did:web:vharsh.github.io:DID:harsh#key-0', '2024-10-24 12:32:38.065994', NULL);
-
-
-INSERT INTO template_data (context, credential_type, template, credential_format, key_manager_app_id, key_manager_ref_id, did_url, cr_dtimes, upd_dtimes) VALUES ('https://www.w3.org/2018/credentials/v1', 'FarmerCredential,VerifiableCredential', '{
- "iss": "${_issuer}",
- "iat": ${_iat},
- "nbf": ${_nbf},
- "exp": ${_exp},
- "vc": {
- "@context": ["https://www.w3.org/ns/credentials/v2"],
- "type": ["VerifiableCredential", "AadhaarCredential"],
- "credentialSubject": {
- "id": "${id}",
- "fullName": "${fullName}",
- "mobileNumber": "${mobileNumber}",
- "dateOfBirth": "${dateOfBirth}",
- "gender": "${gender}",
- "state": "${state}",
- "district": "${district}",
- "villageOrTown": "${villageOrTown}",
- "postalCode": "${postalCode}",
- "landArea": "${landArea}",
- "landOwnershipType": "${landOwnershipType}",
- "primaryCropType": "${primaryCropType}",
- "secondaryCropType": "${secondaryCropType}",
- "face": "${face}",
- "farmerID": "${farmerID}"
- }
- }
- }
-', 'dc+sd-jwt', 'CERTIFY_VC_SIGN_EC_K1','EC_SECP256K1_SIGN','did:web:vharsh.github.io:DID:harsh#key-0', '2024-10-24 12:32:38.065994', NULL);
diff --git a/db_scripts/inji_certify/combined.sql b/db_scripts/local-setup/combined.sql
similarity index 100%
rename from db_scripts/inji_certify/combined.sql
rename to db_scripts/local-setup/combined.sql
diff --git a/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_rollback.sql b/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_rollback.sql
index 629dbc67a..bb7680424 100644
--- a/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_rollback.sql
+++ b/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_rollback.sql
@@ -68,3 +68,50 @@ COMMENT ON COLUMN credential_template.credential_type IS 'Credential Type: Crede
COMMENT ON COLUMN credential_template.template IS 'Template Content: Velocity Template to generate the VC';
COMMENT ON COLUMN credential_template.cr_dtimes IS 'Date when the template was inserted in table.';
COMMENT ON COLUMN credential_template.upd_dtimes IS 'Date when the template was last updated in table.';
+
+
+-- Indexes for credential_status_transaction
+DROP INDEX IF EXISTS certify.idx_cst_credential_id;
+DROP INDEX IF EXISTS certify.idx_cst_status_purpose;
+DROP INDEX IF EXISTS certify.idx_cst_status_list_credential_id;
+DROP INDEX IF EXISTS certify.idx_cst_status_list_index;
+DROP INDEX IF EXISTS certify.idx_cst_cr_dtimes;
+DROP INDEX IF EXISTS certify.idx_cst_status_value;
+
+-- Indexes for status_list_available_indices
+DROP INDEX IF EXISTS certify.idx_sla_available_indices;
+DROP INDEX IF EXISTS certify.idx_sla_status_list_credential_id;
+DROP INDEX IF EXISTS certify.idx_sla_is_assigned;
+DROP INDEX IF EXISTS certify.idx_sla_list_index;
+DROP INDEX IF EXISTS certify.idx_sla_cr_dtimes;
+
+-- Indexes for ledger
+DROP INDEX IF EXISTS certify.idx_ledger_credential_id;
+DROP INDEX IF EXISTS certify.idx_ledger_issuer_id;
+DROP INDEX IF EXISTS certify.idx_ledger_credential_type;
+DROP INDEX IF EXISTS certify.idx_ledger_issue_date;
+DROP INDEX IF EXISTS certify.idx_ledger_expiration_date;
+DROP INDEX IF EXISTS certify.idx_ledger_cr_dtimes;
+DROP INDEX IF EXISTS certify.idx_gin_ledger_indexed_attrs;
+DROP INDEX IF EXISTS certify.idx_gin_ledger_status_details;
+
+-- Indexes for status_list_credential
+DROP INDEX IF EXISTS certify.idx_slc_status_purpose;
+DROP INDEX IF EXISTS certify.idx_slc_credential_type;
+DROP INDEX IF EXISTS certify.idx_slc_credential_status;
+DROP INDEX IF EXISTS certify.idx_slc_cr_dtimes;
+
+
+-- ========= Step 2: Drop the newly created tables =========
+-- The order is important to respect foreign key constraints.
+-- We drop tables with foreign keys first, then the tables they reference.
+
+DROP TABLE IF EXISTS certify.credential_status_transaction;
+DROP TABLE IF EXISTS certify.status_list_available_indices;
+DROP TABLE IF EXISTS certify.ledger;
+DROP TABLE IF EXISTS certify.status_list_credential;
+
+
+-- ========= Step 3: Drop the custom ENUM type =========
+-- This can only be done after all tables using the type have been dropped.
+DROP TYPE IF EXISTS certify.credential_status_enum;
\ No newline at end of file
diff --git a/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_upgrade.sql b/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_upgrade.sql
index 4584b6919..6be819923 100644
--- a/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_upgrade.sql
+++ b/db_upgrade_script/mosip_certify/sql/0.11.0_to_0.12.0_upgrade.sql
@@ -83,3 +83,158 @@ COMMENT ON COLUMN credential_config.claims IS 'Claims: JSON object containing su
COMMENT ON COLUMN credential_config.plugin_configurations IS 'Plugin Configurations: Array of JSON objects for plugin configurations.';
COMMENT ON COLUMN credential_config.cr_dtimes IS 'Created DateTime: Date and time when the config was inserted in table.';
COMMENT ON COLUMN credential_config.upd_dtimes IS 'Updated DateTime: Date and time when the config was last updated in table.';
+
+-- Create ENUM type for credential status
+CREATE TYPE credential_status_enum AS ENUM ('available', 'full');
+
+-- Create status_list_credential table
+CREATE TABLE status_list_credential (
+ id VARCHAR(255) PRIMARY KEY, -- The unique ID (URL/DID/URN) extracted from the VC's 'id' field.
+ vc_document bytea NOT NULL, -- Stores the entire Verifiable Credential JSON document.
+ credential_type VARCHAR(100) NOT NULL, -- Type of the status list (e.g., 'StatusList2021Credential')
+ status_purpose VARCHAR(100), -- Intended purpose of this list within the system (e.g., 'revocation', 'suspension', 'general'). NULLABLE.
+ capacity BIGINT, --- length of status list
+ credential_status credential_status_enum, -- Use the created ENUM type here
+ cr_dtimes timestamp NOT NULL default now(),
+ upd_dtimes timestamp -- When this VC record was last updated in the system
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE status_list_credential IS 'Stores full Status List Verifiable Credentials, including their type and intended purpose within the system.';
+COMMENT ON COLUMN status_list_credential.id IS 'Unique identifier (URL/DID/URN) of the Status List VC (extracted from vc_document.id). Primary Key.';
+COMMENT ON COLUMN status_list_credential.vc_document IS 'The complete JSON document of the Status List Verifiable Credential.';
+COMMENT ON COLUMN status_list_credential.credential_type IS 'The type of the Status List credential, often found in vc_document.type (e.g., StatusList2021Credential).';
+COMMENT ON COLUMN status_list_credential.status_purpose IS 'The intended purpose assigned to this entire Status List within the system (e.g., revocation, suspension, general). This may be based on convention or system policy, distinct from the credentialStatus.statusPurpose used by individual credentials.';
+COMMENT ON COLUMN status_list_credential.cr_dtimes IS 'Timestamp when this Status List VC was first added/fetched into the local system.';
+COMMENT ON COLUMN status_list_credential.upd_dtimes IS 'Timestamp when this Status List VC record was last updated.';
+
+-- Create indexes
+CREATE INDEX IF NOT EXISTS idx_slc_status_purpose ON status_list_credential(status_purpose);
+CREATE INDEX IF NOT EXISTS idx_slc_credential_type ON status_list_credential(credential_type);
+CREATE INDEX IF NOT EXISTS idx_slc_credential_status ON status_list_credential(credential_status);
+CREATE INDEX IF NOT EXISTS idx_slc_cr_dtimes ON status_list_credential(cr_dtimes);
+
+CREATE TABLE IF NOT EXISTS credential_status_transaction (
+ transaction_log_id SERIAL PRIMARY KEY, -- Unique ID for this transaction log entry
+ credential_id VARCHAR(255) NOT NULL, -- The ID of the credential this transaction pertains to (should exist in ledger.credential_id)
+ status_purpose VARCHAR(100), -- The purpose of this status update
+ status_value boolean, -- The status value (true/false)
+ status_list_credential_id VARCHAR(255), -- The ID of the status list credential involved, if any
+ status_list_index BIGINT, -- The index on the status list, if any
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp
+ upd_dtimes TIMESTAMP, -- Update timestamp
+
+ -- Foreign key constraint to ledger table
+ CONSTRAINT fk_credential_status_transaction_ledger
+ FOREIGN KEY(credential_id)
+ REFERENCES ledger(credential_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+
+ -- Foreign key constraint to status_list_credential table
+ CONSTRAINT fk_credential_status_transaction_status_list
+ FOREIGN KEY(status_list_credential_id)
+ REFERENCES status_list_credential(id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE credential_status_transaction IS 'Transaction log for credential status changes and updates.';
+COMMENT ON COLUMN credential_status_transaction.transaction_log_id IS 'Serial primary key for the transaction log entry.';
+COMMENT ON COLUMN credential_status_transaction.credential_id IS 'The ID of the credential this transaction pertains to (references ledger.credential_id).';
+COMMENT ON COLUMN credential_status_transaction.status_purpose IS 'The purpose of this status update (e.g., revocation, suspension).';
+COMMENT ON COLUMN credential_status_transaction.status_value IS 'The status value (true for revoked/suspended, false for active).';
+COMMENT ON COLUMN credential_status_transaction.status_list_credential_id IS 'The ID of the status list credential involved, if any.';
+COMMENT ON COLUMN credential_status_transaction.status_list_index IS 'The index on the status list, if any.';
+COMMENT ON COLUMN credential_status_transaction.cr_dtimes IS 'Timestamp when this transaction was created.';
+COMMENT ON COLUMN credential_status_transaction.upd_dtimes IS 'Timestamp when this transaction was last updated.';
+
+-- Create indexes for credential_status_transaction
+CREATE INDEX IF NOT EXISTS idx_cst_credential_id ON credential_status_transaction(credential_id);
+CREATE INDEX IF NOT EXISTS idx_cst_status_purpose ON credential_status_transaction(status_purpose);
+CREATE INDEX IF NOT EXISTS idx_cst_status_list_credential_id ON credential_status_transaction(status_list_credential_id);
+CREATE INDEX IF NOT EXISTS idx_cst_status_list_index ON credential_status_transaction(status_list_index);
+CREATE INDEX IF NOT EXISTS idx_cst_cr_dtimes ON credential_status_transaction(cr_dtimes);
+CREATE INDEX IF NOT EXISTS idx_cst_status_value ON credential_status_transaction(status_value);
+
+CREATE TABLE status_list_available_indices (
+ id SERIAL PRIMARY KEY, -- Serial primary key
+ status_list_credential_id VARCHAR(255) NOT NULL, -- References status_list_credential.id
+ list_index BIGINT NOT NULL, -- The numerical index within the status list
+ is_assigned BOOLEAN NOT NULL DEFAULT FALSE, -- Flag indicating if this index has been assigned
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp
+ upd_dtimes TIMESTAMP, -- Update timestamp
+
+ -- Foreign key constraint
+ CONSTRAINT fk_status_list_credential
+ FOREIGN KEY(status_list_credential_id)
+ REFERENCES status_list_credential(id)
+ ON DELETE CASCADE -- If a status list credential is deleted, its available index entries are also deleted.
+ ON UPDATE CASCADE, -- If the ID of a status list credential changes, update it here too.
+
+ -- Unique constraint to ensure each index within a list is represented only once
+ CONSTRAINT uq_list_id_and_index
+ UNIQUE (status_list_credential_id, list_index)
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE status_list_available_indices IS 'Helper table to manage and assign available indices from status list credentials.';
+COMMENT ON COLUMN status_list_available_indices.id IS 'Serial primary key for the available index entry.';
+COMMENT ON COLUMN status_list_available_indices.status_list_credential_id IS 'Identifier of the status list credential this index belongs to (FK to status_list_credential.id).';
+COMMENT ON COLUMN status_list_available_indices.list_index IS 'The numerical index (e.g., 0 to N-1) within the specified status list.';
+COMMENT ON COLUMN status_list_available_indices.is_assigned IS 'Flag indicating if this specific index has been assigned (TRUE) or is available (FALSE).';
+COMMENT ON COLUMN status_list_available_indices.cr_dtimes IS 'Timestamp when this index entry record was created (typically when the parent status list was populated).';
+COMMENT ON COLUMN status_list_available_indices.upd_dtimes IS 'Timestamp when this index entry record was last updated (e.g., when is_assigned changed).';
+
+-- Create indexes for status_list_available_indices
+-- Partial index specifically for finding available slots
+CREATE INDEX IF NOT EXISTS idx_sla_available_indices
+ ON status_list_available_indices (status_list_credential_id, is_assigned, list_index)
+ WHERE is_assigned = FALSE;
+
+-- Additional indexes for performance
+CREATE INDEX IF NOT EXISTS idx_sla_status_list_credential_id ON status_list_available_indices(status_list_credential_id);
+CREATE INDEX IF NOT EXISTS idx_sla_is_assigned ON status_list_available_indices(is_assigned);
+CREATE INDEX IF NOT EXISTS idx_sla_list_index ON status_list_available_indices(list_index);
+CREATE INDEX IF NOT EXISTS idx_sla_cr_dtimes ON status_list_available_indices(cr_dtimes);
+
+
+CREATE TABLE ledger (
+ id SERIAL PRIMARY KEY, -- Auto-incrementing serial primary key
+ credential_id VARCHAR(255) NOT NULL, -- Unique ID of the Verifiable Credential WHOSE STATUS IS BEING TRACKED
+ issuer_id VARCHAR(255) NOT NULL, -- Issuer of the TRACKED credential
+ issue_date TIMESTAMPTZ NOT NULL, -- Issuance date of the TRACKED credential
+ expiration_date TIMESTAMPTZ, -- Expiration date of the TRACKED credential, if any
+ credential_type VARCHAR(100) NOT NULL, -- Type of the TRACKED credential (e.g., 'VerifiableId')
+ indexed_attributes JSONB, -- Optional searchable attributes from the TRACKED credential
+ credential_status_details JSONB NOT NULL DEFAULT '[]'::jsonb, -- Stores a list of status objects for this credential, defaults to an empty array.
+ cr_dtimes TIMESTAMP NOT NULL DEFAULT NOW(), -- Creation timestamp of this ledger entry for the tracked credential
+
+ -- Constraints
+ CONSTRAINT uq_ledger_tracked_credential_id UNIQUE (credential_id), -- Ensure tracked credential_id is unique
+ CONSTRAINT ensure_credential_status_details_is_array CHECK (jsonb_typeof(credential_status_details) = 'array') -- Ensure it's always a JSON array
+);
+
+-- Add comments for documentation
+COMMENT ON TABLE ledger IS 'Stores intrinsic information about tracked Verifiable Credentials and their status history.';
+COMMENT ON COLUMN ledger.id IS 'Serial primary key for the ledger table.';
+COMMENT ON COLUMN ledger.credential_id IS 'Unique identifier of the Verifiable Credential whose status is being tracked. Must be unique across the table.';
+COMMENT ON COLUMN ledger.issuer_id IS 'Identifier of the issuer of the tracked credential.';
+COMMENT ON COLUMN ledger.issue_date IS 'Issuance date of the tracked credential.';
+COMMENT ON COLUMN ledger.expiration_date IS 'Expiration date of the tracked credential, if applicable.';
+COMMENT ON COLUMN ledger.credential_type IS 'The type(s) of the tracked credential (e.g., VerifiableId, ProofOfEnrollment).';
+COMMENT ON COLUMN ledger.indexed_attributes IS 'Stores specific attributes extracted from the tracked credential for optimized searching.';
+COMMENT ON COLUMN ledger.credential_status_details IS 'An array of status objects, guaranteed to be a JSON array (list). Defaults to an empty list []. Each object can contain: status_purpose, status_value (boolean), status_list_credential_id, status_list_index, cr_dtimes, upd_dtimes.';
+COMMENT ON COLUMN ledger.cr_dtimes IS 'Timestamp of when this ledger record for the tracked credential was created.';
+
+-- Create indexes for ledger
+CREATE INDEX IF NOT EXISTS idx_ledger_credential_id ON ledger(credential_id);
+CREATE INDEX IF NOT EXISTS idx_ledger_issuer_id ON ledger(issuer_id);
+CREATE INDEX IF NOT EXISTS idx_ledger_credential_type ON ledger(credential_type);
+CREATE INDEX IF NOT EXISTS idx_ledger_issue_date ON ledger(issue_date);
+CREATE INDEX IF NOT EXISTS idx_ledger_expiration_date ON ledger(expiration_date);
+CREATE INDEX IF NOT EXISTS idx_ledger_cr_dtimes ON ledger(cr_dtimes);
+CREATE INDEX IF NOT EXISTS idx_gin_ledger_indexed_attrs ON ledger USING GIN (indexed_attributes);
+CREATE INDEX IF NOT EXISTS idx_gin_ledger_status_details ON ledger USING GIN (credential_status_details);
+