diff --git a/build.gradle b/build.gradle index 572a481..31d7aa5 100644 --- a/build.gradle +++ b/build.gradle @@ -40,9 +40,10 @@ ext { dependencies { // implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' // //config -// implementation 'org.springframework.cloud:spring-cloud-starter-config' -// implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp' + implementation 'org.springframework.cloud:spring-cloud-starter-config' + implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp' //Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.0' @@ -50,6 +51,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' //mysql // implementation 'mysql:mysql-connector-java:8.0.33' + //redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' //actuator implementation 'org.springframework.boot:spring-boot-starter-actuator' @@ -60,7 +63,9 @@ dependencies { implementation 'org.json:json:20240303' // grpc interface - implementation "com.doubleo.grpc:tenant-interface:0.2.5" + implementation "com.doubleo.grpc:tenant-interface:0.3.3" + implementation "com.doubleo.grpc:acapy-interface:0.3.3" + implementation "com.doubleo.grpc:pass-interface:0.3.3" // gRPC 런타임 implementation "io.grpc:grpc-netty:1.63.0" @@ -71,6 +76,10 @@ dependencies { implementation 'com.google.protobuf:protobuf-java:3.24.4' implementation 'net.devh:grpc-spring-boot-starter:3.1.0.RELEASE' + // json + implementation 'com.fasterxml.jackson.core:jackson-core:2.15.2' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/doubleo/didagent/agent/AcapyAgent.java b/src/main/java/com/doubleo/didagent/agent/AcapyAgent.java deleted file mode 100644 index 01205a2..0000000 --- a/src/main/java/com/doubleo/didagent/agent/AcapyAgent.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.doubleo.didagent.agent; - -public class AcapyAgent {} diff --git a/src/main/java/com/doubleo/didagent/agent/client/AcapyClient.java b/src/main/java/com/doubleo/didagent/agent/client/AcapyClient.java new file mode 100644 index 0000000..adf4f67 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/agent/client/AcapyClient.java @@ -0,0 +1,13 @@ +package com.doubleo.didagent.agent.client; + +import lombok.Getter; +import org.springframework.web.reactive.function.client.WebClient; + +@Getter +public class AcapyClient { + private final WebClient webClient; + + public AcapyClient(WebClient webClient) { + this.webClient = webClient; + } +} diff --git a/src/main/java/com/doubleo/didagent/agent/client/AcapyClientConfig.java b/src/main/java/com/doubleo/didagent/agent/client/AcapyClientConfig.java new file mode 100644 index 0000000..8bb5f36 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/agent/client/AcapyClientConfig.java @@ -0,0 +1,27 @@ +package com.doubleo.didagent.agent.client; + +import com.doubleo.didagent.infra.config.hospital.HospitalProperties; +import com.doubleo.didagent.infra.config.mediator.MediatorProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +@RequiredArgsConstructor +public class AcapyClientConfig { + private final MediatorProperties mediatorProperties; + private final HospitalProperties hospitalProperties; + + @Bean("mediatorClient") + public AcapyClient mediatorClient(WebClient.Builder builder) { + WebClient wc = builder.baseUrl(mediatorProperties.adminUrl()).build(); + return new AcapyClient(wc); + } + + @Bean("hospitalClient") + public AcapyClient hospitalClient(WebClient.Builder builder) { + WebClient wc = builder.baseUrl(hospitalProperties.adminUrl()).build(); + return new AcapyClient(wc); + } +} diff --git a/src/main/java/com/doubleo/didagent/controller/AcapyWebhookController.java b/src/main/java/com/doubleo/didagent/controller/AcapyWebhookController.java new file mode 100644 index 0000000..9bebdab --- /dev/null +++ b/src/main/java/com/doubleo/didagent/controller/AcapyWebhookController.java @@ -0,0 +1,27 @@ +package com.doubleo.didagent.controller; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/webhooks") +@Slf4j +public class AcapyWebhookController { + + @PostMapping("/**") + @ResponseStatus(HttpStatus.OK) + public void receiveAnyWebhook( + HttpServletRequest req, + @RequestBody Map payload, + @RequestHeader Map headers) { + String path = req.getRequestURI(); + log.info("=== Webhook Path: {} ===", path); + log.info("--- Headers ---"); + headers.forEach((k, v) -> log.info("{}: {}", k, v)); + log.info("--- Payload ---"); + log.info("{}", payload); + } +} diff --git a/src/main/java/com/doubleo/didagent/controller/DidController.java b/src/main/java/com/doubleo/didagent/controller/DidController.java index 73384f5..ea5a6d7 100644 --- a/src/main/java/com/doubleo/didagent/controller/DidController.java +++ b/src/main/java/com/doubleo/didagent/controller/DidController.java @@ -1,7 +1,7 @@ package com.doubleo.didagent.controller; -import com.doubleo.didagent.dto.request.DidCreateRequest; -import com.doubleo.didagent.dto.response.DidCreateResponse; +import com.doubleo.didagent.dto.request.did.DidCreateRequest; +import com.doubleo.didagent.dto.response.did.DidCreateResponse; import com.doubleo.didagent.service.DidService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/doubleo/didagent/controller/MemberPollController.java b/src/main/java/com/doubleo/didagent/controller/MemberPollController.java new file mode 100644 index 0000000..b1bf20a --- /dev/null +++ b/src/main/java/com/doubleo/didagent/controller/MemberPollController.java @@ -0,0 +1,28 @@ +package com.doubleo.didagent.controller; + +import com.doubleo.didagent.dto.request.poll.HospitalInvitationInfoRequest; +import com.doubleo.didagent.dto.response.poll.InvitationInfoResponse; +import com.doubleo.didagent.service.MemberPollService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/polls") +@RequiredArgsConstructor +@Slf4j +public class MemberPollController { + + private final MemberPollService memberPollService; + + @GetMapping("/mediator-invitation") + public InvitationInfoResponse mediatorInvitationInfoGet() { + return memberPollService.getMediatorInvitation(); + } + + @PostMapping("/hospital-invitation") + public InvitationInfoResponse hospitalInvitationInfoGet( + @RequestBody HospitalInvitationInfoRequest request) { + return memberPollService.getHospitalInvitation(request); + } +} diff --git a/src/main/java/com/doubleo/didagent/domain/domain/ConnectionStatus.java b/src/main/java/com/doubleo/didagent/domain/domain/ConnectionStatus.java new file mode 100644 index 0000000..9095e14 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/domain/domain/ConnectionStatus.java @@ -0,0 +1,28 @@ +package com.doubleo.didagent.domain.domain; + +public enum ConnectionStatus { + VC_OFFERED("vc_offered"), + VC_ISSUED("vc_issued"), + ACTIVE("active"), + INACTIVE("inactive"), + ERROR("error"); + + private final String value; + + ConnectionStatus(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static ConnectionStatus fromValue(String value) { + for (ConnectionStatus status : ConnectionStatus.values()) { + if (status.value.equalsIgnoreCase(value)) { + return status; + } + } + throw new IllegalArgumentException("Unknown ConnectionStatus: " + value); + } +} diff --git a/src/main/java/com/doubleo/didagent/domain/domain/HospitalInvitation.java b/src/main/java/com/doubleo/didagent/domain/domain/HospitalInvitation.java new file mode 100644 index 0000000..12c4c22 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/domain/domain/HospitalInvitation.java @@ -0,0 +1,54 @@ +package com.doubleo.didagent.domain.domain; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; +import org.springframework.data.redis.core.index.Indexed; + +@Getter +@RedisHash("hospital-invitation") +public class HospitalInvitation { + + @Id private final String invitationId; + private final String invitationUrl; + private final String memberId; + @Indexed private final String passId; + @Indexed private final String tenantId; + + @TimeToLive private final long ttl; + + @Builder(access = AccessLevel.PRIVATE) + private HospitalInvitation( + String invitationId, + String invitationUrl, + String tenantId, + String passId, + String memberId, + long ttl) { + this.invitationId = invitationId; + this.invitationUrl = invitationUrl; + this.tenantId = tenantId; + this.passId = passId; + this.memberId = memberId; + this.ttl = ttl; + } + + public static HospitalInvitation createHospitalInvitation( + String invitationId, + String invitationUrl, + String tenantId, + String passId, + String memberId) { + return builder() + .invitationId(invitationId) + .invitationUrl(invitationUrl) + .tenantId(tenantId) + .passId(passId) + .memberId(memberId) + .ttl(1000L * 3600 * 24 * 3) + .build(); + } +} diff --git a/src/main/java/com/doubleo/didagent/domain/domain/MemberConnection.java b/src/main/java/com/doubleo/didagent/domain/domain/MemberConnection.java new file mode 100644 index 0000000..c0463a1 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/domain/domain/MemberConnection.java @@ -0,0 +1,61 @@ +package com.doubleo.didagent.domain.domain; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; +import org.springframework.data.redis.core.index.Indexed; + +@Getter +@RedisHash("connection") +public class MemberConnection { + + @Id private final String connectionId; + private final String tenantId; + @Indexed private final String passId; + private final String memberId; + private final ConnectionStatus connectionStatus; + + @TimeToLive private final long ttl; + + @Builder(access = AccessLevel.PRIVATE) + private MemberConnection( + String tenantId, + String passId, + String memberId, + String connectionId, + ConnectionStatus connectionStatus, + long ttl) { + this.connectionId = connectionId; + this.tenantId = tenantId; + this.passId = passId; + this.memberId = memberId; + this.connectionStatus = connectionStatus; + this.ttl = ttl; + } + + public static MemberConnection createMemberConnection( + String connectionId, String tenantId, String passId, String memberId) { + return builder() + .connectionId(connectionId) + .tenantId(tenantId) + .passId(passId) + .memberId(memberId) + .connectionStatus(ConnectionStatus.ACTIVE) + .ttl(1000L * 3600 * 24 * 3) + .build(); + } + + public MemberConnection updateConnectionStatus(ConnectionStatus newStatus) { + return builder() + .connectionId(this.connectionId) + .tenantId(this.tenantId) + .passId(this.passId) + .memberId(this.memberId) + .connectionStatus(newStatus) + .ttl(this.ttl) + .build(); + } +} diff --git a/src/main/java/com/doubleo/didagent/domain/repository/HospitalInvitationRepository.java b/src/main/java/com/doubleo/didagent/domain/repository/HospitalInvitationRepository.java new file mode 100644 index 0000000..9070c18 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/domain/repository/HospitalInvitationRepository.java @@ -0,0 +1,15 @@ +package com.doubleo.didagent.domain.repository; + +import com.doubleo.didagent.domain.domain.HospitalInvitation; +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; + +public interface HospitalInvitationRepository extends CrudRepository { + + Optional findByInvitationId(String invitationId); + + Optional findHospitalInvitationByPassIdAndTenantId( + String passId, String tenantId); + + void deleteHospitalInvitationByInvitationId(String invitationId); +} diff --git a/src/main/java/com/doubleo/didagent/domain/repository/MemberConnectionRepository.java b/src/main/java/com/doubleo/didagent/domain/repository/MemberConnectionRepository.java new file mode 100644 index 0000000..ac5957e --- /dev/null +++ b/src/main/java/com/doubleo/didagent/domain/repository/MemberConnectionRepository.java @@ -0,0 +1,9 @@ +package com.doubleo.didagent.domain.repository; + +import com.doubleo.didagent.domain.domain.MemberConnection; +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; + +public interface MemberConnectionRepository extends CrudRepository { + Optional findMemberConnectionByConnectionId(String connectionId); +} diff --git a/src/main/java/com/doubleo/didagent/dto/request/did/DidCreateRequest.java b/src/main/java/com/doubleo/didagent/dto/request/did/DidCreateRequest.java new file mode 100644 index 0000000..491fced --- /dev/null +++ b/src/main/java/com/doubleo/didagent/dto/request/did/DidCreateRequest.java @@ -0,0 +1,8 @@ +package com.doubleo.didagent.dto.request.did; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record DidCreateRequest( + @NotNull List routingKeys, @NotBlank String serviceEndpoint) {} diff --git a/src/main/java/com/doubleo/didagent/dto/request/hospital/HospitalInvitationCreateRequest.java b/src/main/java/com/doubleo/didagent/dto/request/hospital/HospitalInvitationCreateRequest.java new file mode 100644 index 0000000..2574d46 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/dto/request/hospital/HospitalInvitationCreateRequest.java @@ -0,0 +1,32 @@ +package com.doubleo.didagent.dto.request.hospital; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public record HospitalInvitationCreateRequest( + @JsonProperty("alias") String alias, + @JsonProperty("handshake_protocols") List handshakeProtocols, + @JsonProperty("goal_code") String goalCode, + @JsonProperty("my_label") String myLabel, + @JsonProperty("accept") List accept, + @JsonProperty("use_did_method") String useDidMethod, + @JsonProperty("multi_use") boolean multiUse) { + public static HospitalInvitationCreateRequest create(String tenantId) { + List handshakeProtocols = new ArrayList<>(); + List accept = new ArrayList<>(); + + handshakeProtocols.add("https://didcomm.org/didexchange/1.0"); + accept.add("didcomm/aip2;env=rfc19"); + + return new HospitalInvitationCreateRequest( + tenantId + ":" + LocalDateTime.now(), + handshakeProtocols, + "vc-issue", + tenantId, + accept, + "did:peer:2", + true); + } +} diff --git a/src/main/java/com/doubleo/didagent/dto/request/poll/HospitalInvitationInfoRequest.java b/src/main/java/com/doubleo/didagent/dto/request/poll/HospitalInvitationInfoRequest.java new file mode 100644 index 0000000..b4339ea --- /dev/null +++ b/src/main/java/com/doubleo/didagent/dto/request/poll/HospitalInvitationInfoRequest.java @@ -0,0 +1,5 @@ +package com.doubleo.didagent.dto.request.poll; + +import jakarta.validation.constraints.NotNull; + +public record HospitalInvitationInfoRequest(@NotNull Long passId, @NotNull Long hospitalId) {} diff --git a/src/main/java/com/doubleo/didagent/dto/response/did/DidCreateResponse.java b/src/main/java/com/doubleo/didagent/dto/response/did/DidCreateResponse.java new file mode 100644 index 0000000..4200e81 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/dto/response/did/DidCreateResponse.java @@ -0,0 +1,10 @@ +package com.doubleo.didagent.dto.response.did; + +import jakarta.validation.constraints.NotBlank; + +public record DidCreateResponse( + @NotBlank String peerDid2, + @NotBlank String signingKeyMb58, + @NotBlank String signingPrivBase58, + @NotBlank String agreementKeyMb58, + @NotBlank String x25519PrivateMb58) {} diff --git a/src/main/java/com/doubleo/didagent/dto/response/poll/InvitationInfoResponse.java b/src/main/java/com/doubleo/didagent/dto/response/poll/InvitationInfoResponse.java new file mode 100644 index 0000000..91278ce --- /dev/null +++ b/src/main/java/com/doubleo/didagent/dto/response/poll/InvitationInfoResponse.java @@ -0,0 +1,5 @@ +package com.doubleo.didagent.dto.response.poll; + +import jakarta.validation.constraints.NotBlank; + +public record InvitationInfoResponse(@NotBlank String invitationUrl) {} diff --git a/src/main/java/com/doubleo/didagent/global/exception/errorcode/AcapyErrorCode.java b/src/main/java/com/doubleo/didagent/global/exception/errorcode/AcapyErrorCode.java new file mode 100644 index 0000000..4ad9e77 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/global/exception/errorcode/AcapyErrorCode.java @@ -0,0 +1,23 @@ +package com.doubleo.didagent.global.exception.errorcode; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum AcapyErrorCode implements BaseErrorCode { + INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "invitation 을 찾을 수 없습니다."), + MEMBER_CONNECTION_NOT_FOUND(HttpStatus.NOT_FOUND, "member connection 을 찾을 수 없습니다."), + INVITATION_REQUEST_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "invitation 생성 요청에 실패했습니다"), + DID_PROCESS_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "DID 생성 요청에 실패했습니다"), + ; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public String errorClassName() { + return this.name(); + } +} diff --git a/src/main/java/com/doubleo/didagent/grpc/client/HospitalTenantClient.java b/src/main/java/com/doubleo/didagent/grpc/client/HospitalTenantClient.java index 3ed5b65..74bac69 100644 --- a/src/main/java/com/doubleo/didagent/grpc/client/HospitalTenantClient.java +++ b/src/main/java/com/doubleo/didagent/grpc/client/HospitalTenantClient.java @@ -1,9 +1,8 @@ package com.doubleo.didagent.grpc.client; -import com.doubleo.tenantservice.domain.tenant.grpc.HospitalTenantServiceGrpc; -import com.doubleo.tenantservice.domain.tenant.grpc.TenantWalletToken; -import com.doubleo.tenantservice.domain.tenant.grpc.UpdateTokensRequest; -import com.doubleo.tenantservice.domain.tenant.grpc.UpdateTokensResponse; +import com.doubleo.tenantservice.domain.tenant.grpc.*; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import java.util.Map; import lombok.extern.slf4j.Slf4j; import net.devh.boot.grpc.client.inject.GrpcClient; @@ -18,18 +17,45 @@ public class HospitalTenantClient { public UpdateTokensResponse updateTokens(Map tokens) { UpdateTokensRequest.Builder builder = UpdateTokensRequest.newBuilder(); + try { + for (Map.Entry entry : tokens.entrySet()) { + TenantWalletToken token = + TenantWalletToken.newBuilder() + .setTenantId(entry.getKey()) + .setWalletToken(entry.getValue()) + .build(); + builder.addTokens(token); + } + return blockingStub.updateTokensByTenantId(builder.build()); + } catch (StatusRuntimeException e) { + throw new StatusRuntimeException( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e)); + } + } + + public GetTokenResponse getTokenByTenantId(String tenantId) { + try { + return blockingStub.getTokenByTenantId( + GetTokensRequest.newBuilder().setTenantId(tenantId).build()); - for (Map.Entry entry : tokens.entrySet()) { - TenantWalletToken token = - TenantWalletToken.newBuilder() - .setTenantId(entry.getKey()) - .setWalletToken(entry.getValue()) - .build(); - builder.addTokens(token); + } catch (StatusRuntimeException e) { + throw new StatusRuntimeException( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e)); } + } - UpdateTokensRequest request = builder.build(); + public String getTenantIdByHospitalId(Long hospitalId) { - return blockingStub.updateTokensByTenantId(request); + try { + HospitalIdToTenantIdResponse response = + blockingStub.getTenantIdByHospitalId( + HospitalIdToTenantIdRequest.newBuilder() + .setHospitalId(hospitalId) + .build()); + return response.getTenantId(); + } catch (Exception e) { + throw new StatusRuntimeException( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e)); + } } } diff --git a/src/main/java/com/doubleo/didagent/grpc/client/PassClient.java b/src/main/java/com/doubleo/didagent/grpc/client/PassClient.java new file mode 100644 index 0000000..25972a5 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/grpc/client/PassClient.java @@ -0,0 +1,38 @@ +package com.doubleo.didagent.grpc.client; + +import com.doubleo.passservice.grpc.server.PassServiceGrpc; +import com.doubleo.passservice.grpc.server.UpdateConnectionStatusRequest; +import com.doubleo.passservice.grpc.server.UpdateConnectionStatusResponse; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.GrpcClient; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class PassClient { + + @GrpcClient("pass-service") + private PassServiceGrpc.PassServiceBlockingStub blockingStub; + + public UpdateConnectionStatusResponse updateConnectionStatus( + String tenantId, Long passId, String connectionId) { + + try { + UpdateConnectionStatusRequest.Builder builder = + UpdateConnectionStatusRequest.newBuilder(); + UpdateConnectionStatusRequest request = + builder.setTenantId(tenantId) + .setPassId(passId) + .setConnectionId(connectionId) + .build(); + return blockingStub.updateConnectionState(request); + + } catch (StatusRuntimeException e) { + log.error("Pass Connection Status 업데이트 실패: {}", e.getMessage()); + throw new StatusRuntimeException( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e)); + } + } +} diff --git a/src/main/java/com/doubleo/didagent/grpc/server/AcapyGrpcServiceImpl.java b/src/main/java/com/doubleo/didagent/grpc/server/AcapyGrpcServiceImpl.java new file mode 100644 index 0000000..a061c9a --- /dev/null +++ b/src/main/java/com/doubleo/didagent/grpc/server/AcapyGrpcServiceImpl.java @@ -0,0 +1,73 @@ +package com.doubleo.didagent.grpc.server; + +import com.doubleo.didagent.agent.HospitalAgent; +import com.doubleo.didagent.domain.domain.ConnectionStatus; +import com.doubleo.didagent.domain.domain.HospitalInvitation; +import com.doubleo.didagent.domain.domain.MemberConnection; +import com.doubleo.didagent.domain.repository.HospitalInvitationRepository; +import com.doubleo.didagent.domain.repository.MemberConnectionRepository; +import com.doubleo.didagent.dto.request.hospital.HospitalInvitationCreateRequest; +import com.doubleo.didagent.dto.response.hospital.HospitalInvitationCreateResponse; +import com.doubleo.didagent.global.exception.CommonException; +import com.doubleo.didagent.global.exception.errorcode.AcapyErrorCode; +import com.doubleo.didagent.grpc.client.HospitalTenantClient; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import net.devh.boot.grpc.server.service.GrpcService; +import org.springframework.transaction.annotation.Transactional; + +@GrpcService +@RequiredArgsConstructor +@Transactional +public class AcapyGrpcServiceImpl extends AcapyServiceGrpc.AcapyServiceImplBase { + + private final HospitalAgent hospitalAgent; + private final HospitalInvitationRepository hospitalInvitationRepository; + private final MemberConnectionRepository memberConnectionRepository; + private final HospitalTenantClient hospitalTenantClient; + + @Override + public void issueVc(VcIssueRequest request, StreamObserver responseObserver) { + + HospitalInvitationCreateResponse invitation = + hospitalAgent + .createHospitalInvitation( + HospitalInvitationCreateRequest.create(request.getTenantId()), + hospitalTenantClient + .getTokenByTenantId(request.getTenantId()) + .getWalletToken()) + .block(); + hospitalInvitationRepository.save( + HospitalInvitation.createHospitalInvitation( + invitation.inviMsgId(), + invitation.invitationUrl(), + request.getTenantId(), + String.valueOf(request.getPassId()), + String.valueOf(request.getMemberId()))); + VcIssueResponse response = + VcIssueResponse.newBuilder().setIsInvitationCreated(true).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void verifyCredential( + VerifyCredentialRequest request, + StreamObserver responseObserver) { + MemberConnection connection = + memberConnectionRepository + .findMemberConnectionByConnectionId(request.getConnectionId()) + .orElseThrow( + () -> + new CommonException( + AcapyErrorCode.MEMBER_CONNECTION_NOT_FOUND)); + if (connection.getConnectionStatus() == ConnectionStatus.VC_ISSUED) { + responseObserver.onNext( + VerifyCredentialResponse.newBuilder().setIsVerified(true).build()); + } else { + responseObserver.onNext( + VerifyCredentialResponse.newBuilder().setIsVerified(false).build()); + } + responseObserver.onCompleted(); + } +} diff --git a/src/main/java/com/doubleo/didagent/infra/config/PropertiesConfig.java b/src/main/java/com/doubleo/didagent/infra/config/PropertiesConfig.java new file mode 100644 index 0000000..86f1984 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/PropertiesConfig.java @@ -0,0 +1,17 @@ +package com.doubleo.didagent.infra.config; + +import com.doubleo.didagent.infra.config.acapy.AcapyProperties; +import com.doubleo.didagent.infra.config.hospital.HospitalProperties; +import com.doubleo.didagent.infra.config.mediator.MediatorProperties; +import com.doubleo.didagent.infra.config.redis.RedisProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@EnableConfigurationProperties({ + MediatorProperties.class, + HospitalProperties.class, + RedisProperties.class, + AcapyProperties.class +}) +@Configuration +public class PropertiesConfig {} diff --git a/src/main/java/com/doubleo/didagent/infra/config/acapy/AcapyProperties.java b/src/main/java/com/doubleo/didagent/infra/config/acapy/AcapyProperties.java new file mode 100644 index 0000000..bd76c23 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/acapy/AcapyProperties.java @@ -0,0 +1,11 @@ +package com.doubleo.didagent.infra.config.acapy; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "acapy") +public record AcapyProperties( + String createInvitation, + String checkConnection, + String createDid, + String postPublicDid, + String issueVc) {} diff --git a/src/main/java/com/doubleo/didagent/infra/config/hospital/HospitalProperties.java b/src/main/java/com/doubleo/didagent/infra/config/hospital/HospitalProperties.java new file mode 100644 index 0000000..c2b7866 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/hospital/HospitalProperties.java @@ -0,0 +1,6 @@ +package com.doubleo.didagent.infra.config.hospital; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "hospital") +public record HospitalProperties(String adminUrl) {} diff --git a/src/main/java/com/doubleo/didagent/infra/config/mediator/MediatorProperties.java b/src/main/java/com/doubleo/didagent/infra/config/mediator/MediatorProperties.java new file mode 100644 index 0000000..2dec40a --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/mediator/MediatorProperties.java @@ -0,0 +1,6 @@ +package com.doubleo.didagent.infra.config.mediator; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "mediator") +public record MediatorProperties(String adminUrl) {} diff --git a/src/main/java/com/doubleo/didagent/infra/config/redis/RedisConfig.java b/src/main/java/com/doubleo/didagent/infra/config/redis/RedisConfig.java new file mode 100644 index 0000000..96f2129 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/redis/RedisConfig.java @@ -0,0 +1,35 @@ +package com.doubleo.didagent.infra.config.redis; + +import java.time.Duration; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +@RequiredArgsConstructor +public class RedisConfig { + + private final RedisProperties redisProperties; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfig = + new RedisStandaloneConfiguration(redisProperties.host(), redisProperties.port()); + + if (!redisProperties.password().isBlank()) { + redisStandaloneConfig.setPassword(redisProperties.password()); + } + + LettuceClientConfiguration lettuceClientConfig = + LettuceClientConfiguration.builder() + .commandTimeout(Duration.ofSeconds(1)) + .shutdownTimeout(Duration.ZERO) + .build(); + + return new LettuceConnectionFactory(redisStandaloneConfig, lettuceClientConfig); + } +} diff --git a/src/main/java/com/doubleo/didagent/infra/config/redis/RedisProperties.java b/src/main/java/com/doubleo/didagent/infra/config/redis/RedisProperties.java new file mode 100644 index 0000000..d57088b --- /dev/null +++ b/src/main/java/com/doubleo/didagent/infra/config/redis/RedisProperties.java @@ -0,0 +1,6 @@ +package com.doubleo.didagent.infra.config.redis; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.data.redis") +public record RedisProperties(String host, int port, String password) {} diff --git a/src/main/java/com/doubleo/didagent/service/DidService.java b/src/main/java/com/doubleo/didagent/service/DidService.java index 9f462e5..14cd517 100644 --- a/src/main/java/com/doubleo/didagent/service/DidService.java +++ b/src/main/java/com/doubleo/didagent/service/DidService.java @@ -1,7 +1,7 @@ package com.doubleo.didagent.service; -import com.doubleo.didagent.dto.request.DidCreateRequest; -import com.doubleo.didagent.dto.response.DidCreateResponse; +import com.doubleo.didagent.dto.request.did.DidCreateRequest; +import com.doubleo.didagent.dto.response.did.DidCreateResponse; import com.doubleo.didagent.global.exception.CommonException; import com.doubleo.didagent.global.exception.errorcode.DidErrorCode; import com.doubleo.didagent.global.util.Ed25519KeyGenerator; diff --git a/src/main/java/com/doubleo/didagent/service/MemberPollService.java b/src/main/java/com/doubleo/didagent/service/MemberPollService.java new file mode 100644 index 0000000..94476c7 --- /dev/null +++ b/src/main/java/com/doubleo/didagent/service/MemberPollService.java @@ -0,0 +1,55 @@ +package com.doubleo.didagent.service; + +import com.doubleo.didagent.agent.MediatorAgent; +import com.doubleo.didagent.domain.repository.HospitalInvitationRepository; +import com.doubleo.didagent.dto.request.mediator.MediatorInvitationCreateRequest; +import com.doubleo.didagent.dto.request.poll.HospitalInvitationInfoRequest; +import com.doubleo.didagent.dto.response.poll.InvitationInfoResponse; +import com.doubleo.didagent.global.exception.CommonException; +import com.doubleo.didagent.global.exception.errorcode.AcapyErrorCode; +import com.doubleo.didagent.grpc.client.HospitalTenantClient; +import java.time.Duration; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class MemberPollService { + + private final MediatorAgent mediatorAgent; + private final HospitalInvitationRepository hospitalInvitationRepository; + private final HospitalTenantClient hospitalTenantClient; + + @Transactional(readOnly = true) + public InvitationInfoResponse getMediatorInvitation() { + return new InvitationInfoResponse( + mediatorAgent + .createMediatorInvitation(MediatorInvitationCreateRequest.generate()) + .block(Duration.ofMillis(10000)) + .invitationUrl()); + } + + @Transactional(readOnly = true) + public InvitationInfoResponse getHospitalInvitation(HospitalInvitationInfoRequest request) { + System.out.println(request.hospitalId()); + System.out.println(request.passId()); + String tenantId = hospitalTenantClient.getTenantIdByHospitalId(request.hospitalId()); + return new InvitationInfoResponse(getHospitalInvitationUrl(request.passId(), tenantId)); + } + + private String getHospitalInvitationUrl(Long passId, String tenantId) { + System.out.println( + hospitalInvitationRepository + .findHospitalInvitationByPassIdAndTenantId(String.valueOf(passId), tenantId) + .orElseThrow( + () -> new CommonException(AcapyErrorCode.INVITATION_NOT_FOUND))); + return hospitalInvitationRepository + .findHospitalInvitationByPassIdAndTenantId(String.valueOf(passId), tenantId) + .orElseThrow(() -> new CommonException(AcapyErrorCode.INVITATION_NOT_FOUND)) + .getInvitationUrl(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 62328f4..64c9568 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,24 +1,21 @@ spring: application: name: did-agent -server: - port: 8080 -management: - endpoints: - web: - exposure: - include: refresh, health, beans, httpexchanges, busrefresh, info, metrics, prometheus + task: + execution: + pool: + core-size: 10 + max-size: 20 + queue-capacity: 100 + config: + import: optional:configserver:${CONFIG_SERVER_URL} -# -# config: -# import: optional:configserver:${CONFIG_SERVER_URL} -# -# rabbitmq: -# host: ${RABBITMQ_HOST:localhost} -# port: ${RABBITMQ_PORT:5672} -# username: ${RABBITMQ_USERNAME:guest} -# password: ${RABBITMQ_PASSWORD:guest} -# -# profiles: -# active: ${SPRING_PROFILES_ACTIVE:local} -# include: ${SPRING_PROFILES_INCLUDE:swagger,datasource,actuator} + rabbitmq: + host: ${RABBITMQ_HOST:localhost} + port: ${RABBITMQ_PORT:5672} + username: ${RABBITMQ_USERNAME:guest} + password: ${RABBITMQ_PASSWORD:guest} + + profiles: + active: ${SPRING_PROFILES_ACTIVE:local} + include: ${SPRING_PROFILES_INCLUDE:swagger,actuator,grpc,redis}