From 44e3f4210d068ffb59522ca1da79179af8ce8935 Mon Sep 17 00:00:00 2001 From: luc662 Date: Sun, 6 Apr 2025 16:14:42 +0000 Subject: [PATCH] se creo el endpoint para la vacuna y el smart contract --- .../chaincode-go/chaincode/smartcontract.go | 99 +++++++++++++ .../controllers/VacunaController.java | 92 ++++++++++++ .../com/code/hyperledger/models/Vacuna.java | 25 ++++ .../code/hyperledger/models/VacunaDto.java | 23 +++ .../hyperledger/services/VacunaService.java | 134 ++++++++++++++++++ 5 files changed, 373 insertions(+) create mode 100644 asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/controllers/VacunaController.java create mode 100644 asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/Vacuna.java create mode 100644 asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/VacunaDto.java create mode 100644 asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/services/VacunaService.java diff --git a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go index bee5e5f7..4fa0d76c 100644 --- a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go +++ b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go @@ -31,6 +31,22 @@ type Receta struct { ExpectedSupplyDuration string `json:"expectedSupplyDuration"` } +type Vacuna struct { + ID string `json:"id"` // identificador único para el ledger + Identificador string `json:"identificador"` + Status string `json:"status"` // podés validarlo con enums si querés + StatusChange string `json:"statusChange"` // como string (ISO8601) + StatusReason string `json:"statusReason"` + VaccinateCode string `json:"vaccinateCode"` + AdministradedProduct string `json:"administradedProduct"` + Manufacturer string `json:"manufacturer"` + LotNumber string `json:"lotNumber"` + ExpirationDate string `json:"expirationDate"` // como string ISO8601 + DniPaciente string `json:"dniPaciente"` + Reactions string `json:"reactions"` // puede ser un string o una estructura si querés después +} + + func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { recetas := []Receta{ { @@ -282,3 +298,86 @@ func (s *SmartContract) GetRecetasPorDniYEstado(ctx contractapi.TransactionConte return recetasFiltradas, nil } + +func (s *SmartContract) CreateVacuna(ctx contractapi.TransactionContextInterface, vacuna Vacuna) error { + exists, err := s.VacunaExists(ctx, vacuna.ID) + if err != nil { + return err + } + if exists { + return fmt.Errorf("la vacuna %s ya existe", vacuna.ID) + } + + vacunaJSON, err := json.Marshal(vacuna) + if err != nil { + return err + } + + return ctx.GetStub().PutState(vacuna.ID, vacunaJSON) +} + +func (s *SmartContract) VacunaExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { + vacunaJSON, err := ctx.GetStub().GetState(id) + if err != nil { + return false, err + } + return vacunaJSON != nil, nil +} + +unc (s *SmartContract) ReadVacuna(ctx contractapi.TransactionContextInterface, id string) (*Vacuna, error) { + vacunaJSON, err := ctx.GetStub().GetState(id) + if err != nil { + return nil, fmt.Errorf("error al leer del ledger: %v", err) + } + if vacunaJSON == nil { + return nil, fmt.Errorf("la vacuna %s no existe", id) + } + + var vacuna Vacuna + err = json.Unmarshal(vacunaJSON, &vacuna) + if err != nil { + return nil, err + } + + return &vacuna, nil +} + +func (s *SmartContract) GetVacunasPorDniYEstado(ctx contractapi.TransactionContextInterface, dni string, estado string) ([]*Vacuna, error) { + if dni == "" { + return nil, fmt.Errorf("el dni es obligatorio") + } + + resultsIterator, err := ctx.GetStub().GetStateByRange("", "") + if err != nil { + return nil, fmt.Errorf("error al obtener datos del ledger: %v", err) + } + defer resultsIterator.Close() + + var vacunasFiltradas []*Vacuna + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + + var vacuna Vacuna + // Solo deserializamos si es posible (podría fallar si no es una vacuna) + if err := json.Unmarshal(queryResponse.Value, &vacuna); err != nil { + continue + } + + // Validamos que tenga un DNI y coincida + if vacuna.DniPaciente != dni { + continue + } + + // Si se pasó un estado, lo filtramos + if estado != "" && vacuna.Status != estado { + continue + } + + vacunasFiltradas = append(vacunasFiltradas, &vacuna) + } + + return vacunasFiltradas, nil +} diff --git a/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/controllers/VacunaController.java b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/controllers/VacunaController.java new file mode 100644 index 00000000..c3423759 --- /dev/null +++ b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/controllers/VacunaController.java @@ -0,0 +1,92 @@ +package com.code.hyperledger.controllers; + +import com.code.hyperledger.Utils.Hashing; +import com.code.hyperledger.models.AssetIdDto; +import com.code.hyperledger.models.Vacuna; +import com.code.hyperledger.services.VacunaService; +import org.hyperledger.fabric.client.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/vacunas") +public class VacunaController { + + @Autowired + private VacunaService vacunaService; + + @PostMapping("/crear") + public ResponseEntity crearVacuna(@RequestBody Vacuna vacuna) { + System.out.println("\n--> Submit Transaction: CrearVacuna"); + + String now = LocalDateTime.now().toString(); + String dni = vacuna.getDniPaciente(); + String id = dni + now; + String assetId = Hashing.sha256(id); + vacuna.setId(assetId); + + AssetIdDto assetIdDto = new AssetIdDto(); + assetIdDto.setDni(dni); + assetIdDto.setTimeStamp(now); + + try { + vacunaService.registrarVacuna(vacuna); + return new ResponseEntity<>(assetIdDto, HttpStatus.OK); + } catch (CommitStatusException | EndorseException | CommitException | SubmitException e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } + + @PostMapping("/obtener") + public ResponseEntity obtenerVacuna(@RequestBody Map requestBody) { + try { + String id = requestBody.get("id"); + if (id == null || id.isEmpty()) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + Vacuna vacuna = vacunaService.obtenerVacuna(id); + return new ResponseEntity<>(vacuna, HttpStatus.OK); + } catch (IOException | GatewayException e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + @GetMapping("/todas") + public ResponseEntity> obtenerTodasLasVacunas() { + try { + List vacunas = vacunaService.obtenerTodasLasVacunas(); + return new ResponseEntity<>(vacunas, HttpStatus.OK); + } catch (IOException | GatewayException e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/filtrar") + public ResponseEntity> obtenerVacunasPorDniYEstado(@RequestBody Map filtros) { + try { + String dni = filtros.get("dni"); + String estado = filtros.get("estado"); // puede venir null o vacío + + if (dni == null || dni.isEmpty()) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + List vacunas = vacunaService.obtenerVacunasPorDniYEstado(dni, estado); + return new ResponseEntity<>(vacunas, HttpStatus.OK); + } catch (IOException | GatewayException e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/Vacuna.java b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/Vacuna.java new file mode 100644 index 00000000..7c9e6e74 --- /dev/null +++ b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/Vacuna.java @@ -0,0 +1,25 @@ +package com.code.hyperledger.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Vacuna { + private String id; + private String identificador; + private String status; + + private String statusChange; + private String statusReason; + private String vaccinateCode; + private String administradedProduct; + private String manufacturer; + private String lotNumber; + private String expirationDate; + private String dniPaciente; + private String reactions; +} + diff --git a/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/VacunaDto.java b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/VacunaDto.java new file mode 100644 index 00000000..a27a34b0 --- /dev/null +++ b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/models/VacunaDto.java @@ -0,0 +1,23 @@ +package com.code.hyperledger.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class VacunaDto { + private String identificador; + private String status; + //@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private String statusChange; + private String statusReason; + private String vaccinateCode; + private String administradedProduct; + private String manufacturer; + private String lotNumber; + private String expirationDate; + private String dniPaciente; + private String reactions; +} diff --git a/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/services/VacunaService.java b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/services/VacunaService.java new file mode 100644 index 00000000..03f7e5b6 --- /dev/null +++ b/asset-transfer-basic/maven-API-SiMeDi/src/main/java/com/code/hyperledger/services/VacunaService.java @@ -0,0 +1,134 @@ +package com.code.hyperledger.services; + +import com.code.hyperledger.models.Vacuna; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.grpc.Grpc; +import io.grpc.ManagedChannel; +import io.grpc.TlsChannelCredentials; +import lombok.SneakyThrows; +import org.hyperledger.fabric.client.*; +import org.hyperledger.fabric.client.identity.*; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.InvalidKeyException; +import java.security.cert.CertificateException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Service +public class VacunaService { + + private static final String MSP_ID = System.getenv().getOrDefault("MSP_ID", "Org1MSP"); + private static final String CHANNEL_NAME = System.getenv().getOrDefault("CHANNEL_NAME", "mychannel"); + private static final String CHAINCODE_NAME = System.getenv().getOrDefault("CHAINCODE_NAME", "basic"); + + private static final Path CRYPTO_PATH = Paths + .get("../../test-network/organizations/peerOrganizations/org1.example.com"); + private static final Path CERT_DIR_PATH = CRYPTO_PATH.resolve("users/User1@org1.example.com/msp/signcerts"); + private static final Path KEY_DIR_PATH = CRYPTO_PATH.resolve("users/User1@org1.example.com/msp/keystore"); + private static final Path TLS_CERT_PATH = CRYPTO_PATH.resolve("peers/peer0.org1.example.com/tls/ca.crt"); + + private static final String PEER_ENDPOINT = "localhost:7051"; + private static final String OVERRIDE_AUTH = "peer0.org1.example.com"; + + private Contract contract; + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + private static Path getFirstFilePath(Path dirPath) throws IOException { + try (var keyFiles = Files.list(dirPath)) { + return keyFiles.findFirst().orElseThrow(); + } + } + + @SneakyThrows + @PostConstruct + public void init() { + var channel = newGrpcConnection(); + + var builder = Gateway.newInstance() + .identity(newIdentity()) + .signer(newSigner()) + .connection(channel) + .evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS)) + .endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS)) + .submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS)) + .commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES)); + + try (var gateway = builder.connect()) { + this.setContract(gateway); + } + } + + private static ManagedChannel newGrpcConnection() throws IOException { + var credentials = TlsChannelCredentials.newBuilder() + .trustManager(TLS_CERT_PATH.toFile()) + .build(); + return Grpc.newChannelBuilder(PEER_ENDPOINT, credentials) + .overrideAuthority(OVERRIDE_AUTH) + .build(); + } + + private Identity newIdentity() throws IOException, CertificateException { + try (var certReader = Files.newBufferedReader(getFirstFilePath(CERT_DIR_PATH))) { + var certificate = Identities.readX509Certificate(certReader); + return new X509Identity(MSP_ID, certificate); + } + } + + private Signer newSigner() throws IOException, InvalidKeyException { + try (var keyReader = Files.newBufferedReader(getFirstFilePath(KEY_DIR_PATH))) { + var privateKey = Identities.readPrivateKey(keyReader); + return Signers.newPrivateKeySigner(privateKey); + } + } + + private void setContract(final Gateway gateway) { + var network = gateway.getNetwork(CHANNEL_NAME); + contract = network.getContract(CHAINCODE_NAME); + } + + public void registrarVacuna(Vacuna vacuna) + throws CommitStatusException, EndorseException, CommitException, SubmitException { + contract.submitTransaction( + "CreateVacuna", + vacuna.getId(), + vacuna.getNombre(), + vacuna.getLaboratorio(), + vacuna.getFechaAplicacion(), + vacuna.getDniPaciente(), + vacuna.getEstado() + ); + } + + public Vacuna obtenerVacuna(String vacunaId) throws GatewayException, IOException { + var evaluateResult = contract.evaluateTransaction("ReadVacuna", vacunaId); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(evaluateResult, Vacuna.class); + } + + public List obtenerTodasLasVacunas() throws GatewayException, IOException { + var evaluateResult = contract.evaluateTransaction("GetAllVacunas"); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(evaluateResult, + objectMapper.getTypeFactory().constructCollectionType(List.class, Vacuna.class)); + } + + public List obtenerVacunasPorDniYEstado(String dni, String estado) throws GatewayException, IOException { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] evaluateResult; + if (estado == null || estado.isBlank()) { + evaluateResult = contract.evaluateTransaction("GetVacunasPorDni", dni); + } else { + evaluateResult = contract.evaluateTransaction("GetVacunasPorDniYEstado", dni, estado); + } + return objectMapper.readValue(evaluateResult, + objectMapper.getTypeFactory().constructCollectionType(List.class, Vacuna.class)); + } +}