From e6a01aa1be66635fe7e5abf18e24af4b2c18fe16 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Sun, 25 Jul 2021 21:36:43 -0700 Subject: [PATCH 1/8] Activity/Task - query from view Add new events and listeners separate from Aries Events Signed-off-by: Jason Sherman --- .../bpa/config/ActivityLogConfig.java | 21 +- .../controller/api/activity/ActivityRole.java | 7 +- .../api/activity/ActivityState.java | 9 +- .../bpa/impl/ActivitiesManager.java | 239 +++++++++--------- .../bpa/impl/NotificationEventListener.java | 89 +++++++ .../hyperledger/bpa/impl/PartnerManager.java | 9 +- .../bpa/impl/aries/ConnectionManager.java | 30 ++- .../bpa/impl/aries/CredentialManager.java | 14 +- .../bpa/impl/aries/ProofEventHandler.java | 23 +- .../notification/CredentialAddedEvent.java | 35 +++ .../impl/notification/PartnerAddedEvent.java | 33 +++ .../notification/PartnerRemovedEvent.java | 33 +++ .../PartnerRequestReceivedEvent.java | 34 +++ .../PresentationRequestCompletedEvent.java | 34 +++ .../PresentationRequestReceivedEvent.java | 34 +++ .../org/hyperledger/bpa/model/Activity.java | 46 ++++ .../bpa/repository/ActivityRepository.java | 42 +++ .../V1.17__add-activity-view.sql | 10 + .../src/main/resources/log4j2.xml | 4 +- 19 files changed, 602 insertions(+), 144 deletions(-) create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/CredentialAddedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java create mode 100644 backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java index fa515ad30..7c7c3d027 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/ActivityLogConfig.java @@ -23,6 +23,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +import java.util.ArrayList; import java.util.List; @Singleton @@ -39,8 +40,10 @@ public class ActivityLogConfig { AcaPyConfig acaPyConfig; public List getConnectionStatesForActivities() { - return connectionStates(ConnectionState.REQUEST, ConnectionState.INVITATION, ConnectionState.ACTIVE, - ConnectionState.RESPONSE); + List results = new ArrayList<>(getConnectionStatesCompleted()); + results.addAll(getConnectionStatesForTasks()); + results.add(ConnectionState.INVITATION); + return List.copyOf(results); } public List getConnectionStatesForTasks() { @@ -51,7 +54,11 @@ public List getConnectionStatesForTasks() { } public List getConnectionStatesCompleted() { - return connectionStates(ConnectionState.ACTIVE, ConnectionState.RESPONSE); + return connectionStates(ConnectionState.ACTIVE, + ConnectionState.RESPONSE, + ConnectionState.COMPLETED, + ConnectionState.PING_RESPONSE, + ConnectionState.PING_NO_RESPONSE); } public boolean isConnectionRequestTask() { @@ -59,10 +66,10 @@ public boolean isConnectionRequestTask() { } public List getPresentationExchangeStatesForActivities() { - return presentationExchangeStates(PresentationExchangeState.REQUEST_RECEIVED, - PresentationExchangeState.REQUEST_SENT, - PresentationExchangeState.VERIFIED, - PresentationExchangeState.PRESENTATION_ACKED); + List results = new ArrayList<>(getPresentationExchangeStatesCompleted()); + results.addAll(getPresentationExchangeStatesForTasks()); + results.add(PresentationExchangeState.REQUEST_SENT); + return List.copyOf(results); } public List getPresentationExchangeStatesForTasks() { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java index 8c65ea4cb..7c07338a1 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java @@ -29,5 +29,10 @@ public enum ActivityRole { @JsonProperty("presentation_exchange_prover") PRESENTATION_EXCHANGE_PROVER, @JsonProperty("presentation_exchange_verifier") - PRESENTATION_EXCHANGE_VERIFIER + PRESENTATION_EXCHANGE_VERIFIER, + @JsonProperty("credential_exchange_holder") + CREDENTIAL_EXCHANGE_HOLDER, + @JsonProperty("credential_exchange_prover") + CREDENTIAL_EXCHANGE_ISSUER, + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java index 74f4d8ad9..b32237752 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java @@ -33,5 +33,12 @@ public enum ActivityState { @JsonProperty("presentation_exchange_received") PRESENTATION_EXCHANGE_RECEIVED, @JsonProperty("presentation_exchange_accepted") - PRESENTATION_EXCHANGE_ACCEPTED + PRESENTATION_EXCHANGE_ACCEPTED, + @JsonProperty("credential_exchange_sent") + CREDENTIAL_EXCHANGE_SENT, + @JsonProperty("credential_exchange_received") + CREDENTIAL_EXCHANGE_RECEIVED, + @JsonProperty("credential_exchange_accepted") + CREDENTIAL_EXCHANGE_ACCEPTED + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java index 7cbdaaf9d..988516091 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java @@ -17,9 +17,7 @@ */ package org.hyperledger.bpa.impl; -import io.micronaut.core.annotation.Nullable; import lombok.NoArgsConstructor; -import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.hyperledger.aries.api.connection.ConnectionState; import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; @@ -27,16 +25,16 @@ import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.config.ActivityLogConfig; import org.hyperledger.bpa.controller.api.activity.*; -import org.hyperledger.bpa.model.Partner; -import org.hyperledger.bpa.model.PartnerProof; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.model.Activity; +import org.hyperledger.bpa.repository.ActivityRepository; import org.hyperledger.bpa.repository.PartnerProofRepository; import org.hyperledger.bpa.repository.PartnerRepository; import javax.inject.Inject; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.UUID; +import java.util.stream.Collectors; @Slf4j @NoArgsConstructor @@ -52,144 +50,143 @@ public class ActivitiesManager { ActivityLogConfig activityLogConfig; @Inject - PartnerManager partnerManager; + ActivityRepository activityRepository; - private List getActivityListItems(@NonNull ActivitySearchParameters parameters) { - List results = new ArrayList<>(); - if (parameters.getActivity() == null || parameters.getActivity()) { - // connection invitations... outgoing. - results.addAll(getConnectionRequests(parameters.getType(), - activityLogConfig.getConnectionStatesForActivities(), false)); - results.addAll( - getPresentationExchanges(parameters.getType(), - activityLogConfig.getPresentationExchangeStatesForActivities(), false)); - } - return results; - } + @Inject + Converter converter; - private List getTaskListItems(@NonNull ActivitySearchParameters parameters) { + public List getItems(ActivitySearchParameters parameters) { List results = new ArrayList<>(); - if (parameters.getTask() == null || parameters.getTask()) { - results.addAll(getConnectionRequests(parameters.getType(), - activityLogConfig.getConnectionStatesForTasks(), true)); - results.addAll(getPresentationExchanges(parameters.getType(), - activityLogConfig.getPresentationExchangeStatesForTasks(), true)); - } + List types = getSearchTypes(parameters); + List states = getSearchStates(parameters); + Iterable activities = activityRepository.findByTypeIn(types); + activities.forEach(activity -> { + // filter based on the appropriate states. + // we can't add states to the query without losing the auto population of the + // partner.. + if (states.contains(activity.getState())) { + PartnerAPI partner = converter.toAPIObject(activity.getPartner()); + // for now, just use the partner as the link.. + ActivityItem item = ActivityItem.builder() + .linkId(partner.getId()) + .partner(partner) + .updatedAt(activity.getUpdatedAt().toEpochMilli()) + .type(getActivityType(activity)) + .role(getActivityRole(activity)) + .state(getActivityState(activity)) + .task(isTask(activity)) + .build(); + results.add(item); + } + }); return results; } - public List getItems(ActivitySearchParameters parameters) { - List results = new ArrayList<>(); - results.addAll(getActivityListItems(parameters)); - results.addAll(getTaskListItems(parameters)); - results.sort(Comparator.comparingLong(ActivityItem::getUpdatedAt).reversed()); - return results; + private ActivityRole getActivityRole(Activity a) { + switch (ActivityType.valueOf(a.getType())) { + case CONNECTION_REQUEST: + return ActivityRole.valueOf(a.getRole()); + case PRESENTATION_EXCHANGE: + default: + PresentationExchangeRole per = PresentationExchangeRole.valueOf(a.getRole()); + switch (per) { + case VERIFIER: + return ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + case PROVER: + default: + return ActivityRole.PRESENTATION_EXCHANGE_PROVER; + } + } } - private List getConnectionRequests(@Nullable ActivityType type, List states, - Boolean incoming) { - List results = new ArrayList<>(); - if (type == null || type == ActivityType.CONNECTION_REQUEST) { - Iterable partners = partnerRepo.findByStateIn(states); - for (Partner p : partners) { - if (incoming) { - // then we are looking for tasks... - if (p.getIncoming() != null) { - results.add(getConnectionRequestItem(p, true)); - } + private ActivityState getActivityState(Activity a) { + switch (ActivityType.valueOf(a.getType())) { + case CONNECTION_REQUEST: + ConnectionState cs = ConnectionState.valueOf(a.getState()); + if (activityLogConfig.getConnectionStatesCompleted().contains(cs)) { + return ActivityState.CONNECTION_REQUEST_ACCEPTED; + } else { + if (ActivityRole.CONNECTION_REQUEST_SENDER.equals(ActivityRole.valueOf(a.getRole()))) { + return ActivityState.CONNECTION_REQUEST_SENT; } else { - results.add(getConnectionRequestItem(p, false)); + return ActivityState.CONNECTION_REQUEST_RECEIVED; + } + } + case PRESENTATION_EXCHANGE: + default: + PresentationExchangeState pes = PresentationExchangeState.valueOf(a.getState()); + switch (pes) { + case VERIFIED: + case PRESENTATION_ACKED: + return ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; + case REQUEST_SENT: + case PRESENTATIONS_SENT: + return ActivityState.PRESENTATION_EXCHANGE_SENT; + case REQUEST_RECEIVED: + case PRESENTATION_RECEIVED: + return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + default: + PresentationExchangeRole per = PresentationExchangeRole.valueOf(a.getRole()); + switch (per) { + case VERIFIER: + return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + case PROVER: + default: + return ActivityState.PRESENTATION_EXCHANGE_SENT; } } } - return results; } - private List getPresentationExchanges(@Nullable ActivityType type, - List states, Boolean task) { - List results = new ArrayList<>(); - if (type == null || type == ActivityType.PRESENTATION_EXCHANGE) { - Iterable proofs = proofRepository.findByStateIn(states); - for (PartnerProof p : proofs) { - results.add(getPresentationExchangeItem(p, task)); - } + private ActivityType getActivityType(Activity a) { + switch (ActivityType.valueOf(a.getType())) { + case CONNECTION_REQUEST: + return ActivityType.CONNECTION_REQUEST; + case PRESENTATION_EXCHANGE: + default: + return ActivityType.PRESENTATION_EXCHANGE; } - return results; } - private ActivityItem getConnectionRequestItem(@NonNull Partner p, Boolean task) { - ActivityRole role = (p.getIncoming() == null) ? ActivityRole.CONNECTION_REQUEST_SENDER - : ActivityRole.CONNECTION_REQUEST_RECIPIENT; - ActivityType type = ActivityType.CONNECTION_REQUEST; - - ActivityState state; - switch (p.getState()) { - case ACTIVE: - case RESPONSE: - state = ActivityState.CONNECTION_REQUEST_ACCEPTED; - break; + private boolean isTask(Activity a) { + switch (ActivityType.valueOf(a.getType())) { + case CONNECTION_REQUEST: + return activityLogConfig.getConnectionStatesForTasks().contains(ConnectionState.valueOf(a.getState())); + case PRESENTATION_EXCHANGE: + return activityLogConfig.getPresentationExchangeStatesForTasks() + .contains(PresentationExchangeState.valueOf(a.getState())); default: - if (role == ActivityRole.CONNECTION_REQUEST_SENDER) { - state = ActivityState.CONNECTION_REQUEST_SENT; - } else { - state = ActivityState.CONNECTION_REQUEST_RECEIVED; - } + return false; } - - Long updatedAt = p.getUpdatedAt().toEpochMilli(); - UUID linkId = p.getId(); - PartnerAPI apiPartner = partnerManager.getPartner(linkId); - return ActivityItem.builder() - .role(role) - .state(state) - .type(type) - .partner(apiPartner) - .linkId(linkId.toString()) - .task(task) - .updatedAt(updatedAt) - .build(); } - private ActivityItem getPresentationExchangeItem(@NonNull PartnerProof p, Boolean task) { - ActivityRole role = p.getRole() == PresentationExchangeRole.PROVER ? ActivityRole.PRESENTATION_EXCHANGE_PROVER - : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; - ActivityType type = ActivityType.PRESENTATION_EXCHANGE; - - ActivityState state; - switch (p.getState()) { - case VERIFIED: - case PRESENTATION_ACKED: - state = ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; - break; - case REQUEST_SENT: - case PRESENTATIONS_SENT: - state = ActivityState.PRESENTATION_EXCHANGE_SENT; - break; - case REQUEST_RECEIVED: - case PRESENTATION_RECEIVED: - state = ActivityState.PRESENTATION_EXCHANGE_RECEIVED; - break; - default: - if (role == ActivityRole.PRESENTATION_EXCHANGE_PROVER) { - state = ActivityState.PRESENTATION_EXCHANGE_SENT; - } else { - state = ActivityState.PRESENTATION_EXCHANGE_RECEIVED; - } + private List getSearchTypes(ActivitySearchParameters parameters) { + if (parameters.getType() != null) { + return List.of(parameters.getType().toString()); } + // TODO: add credential offers in when we support it + return List.of(ActivityType.PRESENTATION_EXCHANGE.toString(), ActivityType.CONNECTION_REQUEST.toString()); + } - UUID linkId = p.getPartnerId(); - PartnerAPI apiPartner = partnerManager.getPartner(linkId); - Long updatedAt = (p.getIssuedAt() != null) ? p.getIssuedAt().toEpochMilli() : p.getCreatedAt().toEpochMilli(); - - return ActivityItem.builder() - .role(role) - .state(state) - .type(type) - .partner(apiPartner) - .linkId(linkId.toString()) - .task(task) - .updatedAt(updatedAt) - .build(); + private List getSearchStates(ActivitySearchParameters parameters) { + List states = new ArrayList<>(); + if (parameters.getActivity()) { + states.addAll(activityLogConfig.getConnectionStatesForActivities().stream() + .map(v -> v.toString()) + .collect(Collectors.toList())); + states.addAll(activityLogConfig.getPresentationExchangeStatesForActivities().stream() + .map(v -> v.toString()) + .collect(Collectors.toList())); + } + if (parameters.getTask()) { + states.addAll(activityLogConfig.getConnectionStatesForTasks().stream() + .map(v -> v.toString()) + .collect(Collectors.toList())); + states.addAll(activityLogConfig.getPresentationExchangeStatesForTasks().stream() + .map(v -> v.toString()) + .collect(Collectors.toList())); + } + return states; } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java new file mode 100644 index 000000000..3728fc745 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl; + +import io.micronaut.runtime.event.annotation.EventListener; +import io.micronaut.scheduling.annotation.Async; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.bpa.impl.notification.*; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +@Slf4j +public class NotificationEventListener { + + @Inject + PartnerManager partnerManager; + + @EventListener + @Async + public void onCredentialAddedEvent(CredentialAddedEvent event) { + log.debug("onCredentialAddedEvent"); + // we have the connection id, but not the partner, will need to look up + // partner... + partnerManager.getPartnerByConnectionId(event.getCredential().getConnectionId()).ifPresent(p -> { + log.debug(p.getDid()); + }); + } + + @EventListener + @Async + public void onPartnerRequestReceivedEvent(PartnerRequestReceivedEvent event) { + log.debug("onPartnerRequestReceivedEvent"); + // we have the partner + } + + @EventListener + @Async + public void onPartnerAddedEvent(PartnerAddedEvent event) { + log.debug("onPartnerAddedEvent"); + // we have the partner + } + + @EventListener + @Async + public void onPartnerRemovedEvent(PartnerRemovedEvent event) { + log.debug("onPartnerRemovedEvent"); + // we have the partner + } + + @EventListener + @Async + public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEvent event) { + log.debug("onPresentationRequestCompletedEvent"); + // we have the partner id, but not the partner, will need to look up partner... + partnerManager.getPartnerById(event.getPartnerProof().getPartnerId()).ifPresent(p -> { + log.debug(p.getDid()); + // send out separate notifications for verified or received. + }); + } + + @EventListener + @Async + public void onPresentationRequestReceivedEvent(PresentationRequestReceivedEvent event) { + log.debug("onPresentationRequestReceivedEvent"); + // we have the partner id, but not the partner, will need to look up partner... + partnerManager.getPartnerById(event.getPartnerProof().getPartnerId()).ifPresent(p -> { + log.debug(p.getDid()); + }); + } + + // send message over socket... +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java index e6fbb1f55..5559e353e 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java @@ -40,7 +40,10 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -84,6 +87,10 @@ public Optional getPartnerById(@NonNull UUID id) { return repo.findById(id).map(converter::toAPIObject); } + public Optional getPartnerByConnectionId(@NonNull String connectionId) { + return repo.findByConnectionId(connectionId).map(converter::toAPIObject); + } + @Nullable public PartnerAPI getPartner(@NonNull UUID id) { return repo.findById(id).map(converter::toAPIObject).orElse(null); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java index e556cfb79..5d2ac07e6 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.CollectionUtils; import lombok.NonNull; @@ -34,12 +35,16 @@ import org.hyperledger.aries.api.out_of_band.CreateInvitationFilter; import org.hyperledger.aries.api.present_proof.PresentProofRecordsFilter; import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; +import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.config.BPAMessageSource; import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.controller.api.partner.CreatePartnerInvitationRequest; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.activity.DidResolver; +import org.hyperledger.bpa.impl.notification.PartnerRequestReceivedEvent; +import org.hyperledger.bpa.impl.notification.PartnerAddedEvent; +import org.hyperledger.bpa.impl.notification.PartnerRemovedEvent; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.Partner; import org.hyperledger.bpa.model.PartnerProof; @@ -91,6 +96,9 @@ public class ConnectionManager { @Inject BPAMessageSource.DefaultMessageSource messageSource; + @Inject + ApplicationEventPublisher eventPublisher; + /** * Creates a connection invitation to be used within a barcode * @@ -131,7 +139,7 @@ public JsonNode createConnectionInvitation(@NonNull CreatePartnerInvitationReque /** * Create a connection based on a did, e.g. did:web or did:indy. - * + * * @param did the fully qualified did like did:indy:123 * @return {@link ConnectionRecord} */ @@ -171,6 +179,11 @@ public void handleOutgoingConnectionEvent(ConnectionRecord record) { } else { partnerRepo.updateState(dbP.getId(), record.getState()); } + // should it be on request, when you ask for new partner, or when partner + // accepts? + if (ConnectionState.REQUEST.equals(record.getState())) { + eventPublisher.publishEvent(PartnerAddedEvent.builder().partner(conv.toAPIObject(dbP)).build()); + } }); } @@ -225,7 +238,11 @@ private void resolveAndSend(ConnectionRecord record, Partner p) { // only incoming connections in state request if (ConnectionState.REQUEST.equals(record.getState())) { didResolver.lookupIncoming(p); - messageService.sendMessage(WebSocketMessageBody.partnerReceived(conv.toAPIObject(p))); + PartnerAPI o = conv.toAPIObject(p); + messageService.sendMessage(WebSocketMessageBody.partnerReceived(o)); + if (record.isIncomingConnection()) { + eventPublisher.publishEvent(PartnerRequestReceivedEvent.builder().partner(o).build()); + } } } @@ -238,7 +255,10 @@ public void removeConnection(String connectionId) { log.warn("Could not delete aries connection.", e); } - partnerRepo.findByConnectionId(connectionId).ifPresent(p -> { + Optional partner = partnerRepo.findByConnectionId(connectionId); + final PartnerAPI[] partnerAPI = { null }; + partner.ifPresent(p -> { + partnerAPI[0] = conv.toAPIObject(p); final List proofs = partnerProofRepo.findByPartnerId(p.getId()); if (CollectionUtils.isNotEmpty(proofs)) { partnerProofRepo.deleteAll(proofs); @@ -262,7 +282,9 @@ public void removeConnection(String connectionId) { }); myCredRepo.updateByConnectionId(connectionId, null); - + if (partnerAPI[0] != null) { + eventPublisher.publishEvent(PartnerRemovedEvent.builder().partner(partnerAPI[0]).build()); + } } catch (IOException e) { log.error("Could not delete connection: {}", connectionId, e); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java index 43d22459f..d85309620 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.annotation.Nullable; import lombok.NonNull; import lombok.Setter; @@ -44,6 +45,7 @@ import org.hyperledger.bpa.impl.activity.LabelStrategy; import org.hyperledger.bpa.impl.activity.VPManager; import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.impl.notification.CredentialAddedEvent; import org.hyperledger.bpa.impl.util.AriesStringUtil; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.MyCredential; @@ -103,6 +105,9 @@ public class CredentialManager { @Inject LabelStrategy labelStrategy; + @Inject + ApplicationEventPublisher eventPublisher; + // request credential from issuer (partner) public void sendCredentialRequest(@NonNull UUID partnerId, @NonNull UUID myDocId) { final Optional dbPartner = partnerRepo.findById(partnerId); @@ -169,7 +174,12 @@ public void handleCredentialAcked(V1CredentialExchange credEx) { .setIssuedAt(Instant.now()) .setLabel(label); MyCredential updated = credRepo.update(cred); - messageService.sendMessage(WebSocketMessageBody.credentialReceived(buildAriesCredential(updated))); + AriesCredential ariesCredential = buildAriesCredential(updated); + messageService.sendMessage(WebSocketMessageBody.credentialReceived(ariesCredential)); + eventPublisher.publishEvent(CredentialAddedEvent.builder() + .credential(ariesCredential) + .credentialExchange(credEx) + .build()); }, () -> log.error("Received credential without matching thread id, credential is not stored.")); } @@ -215,7 +225,7 @@ private AriesCredential buildAriesCredential(MyCredential dbCred) { /** * Updates the credentials label - * + * * @param id the credential id * @param label the credentials label * @return the updated credential if found diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java index 39d5fdc9c..1071408a9 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java @@ -17,6 +17,7 @@ */ package org.hyperledger.bpa.impl.aries; +import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.util.CollectionUtils; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -26,6 +27,8 @@ import org.hyperledger.aries.api.present_proof.PresentationExchangeState; import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.MessageService; +import org.hyperledger.bpa.impl.notification.PresentationRequestCompletedEvent; +import org.hyperledger.bpa.impl.notification.PresentationRequestReceivedEvent; import org.hyperledger.bpa.model.PartnerProof; import org.hyperledger.bpa.repository.PartnerProofRepository; import org.hyperledger.bpa.repository.PartnerRepository; @@ -50,6 +53,9 @@ public class ProofEventHandler { @Inject MessageService messageService; + @Inject + ApplicationEventPublisher eventPublisher; + void dispatch(PresentationExchangeRecord proof) { if (proof.isVerified() && PresentationExchangeRole.VERIFIER.equals(proof.getRole()) || PresentationExchangeState.PRESENTATION_ACKED.equals(proof.getState()) @@ -65,7 +71,7 @@ void dispatch(PresentationExchangeRecord proof) { /** * Default proof event handler that either stores or updates partner proofs - * + * * @param proof {@link PresentationExchangeRecord} */ private void handleAll(PresentationExchangeRecord proof) { @@ -87,7 +93,7 @@ private void handleAll(PresentationExchangeRecord proof) { /** * Handles all events that are either acked or verified connectionless proofs * are currently not handled - * + * * @param proof {@link PresentationExchangeRecord} */ private void handleAckedOrVerified(PresentationExchangeRecord proof) { @@ -102,6 +108,9 @@ private void handleAckedOrVerified(PresentationExchangeRecord proof) { state, WebSocketMessageBody.WebSocketMessageType.PROOF, savedProof); + eventPublisher.publishEvent(PresentationRequestCompletedEvent.builder() + .partnerProof(savedProof) + .build()); } else { log.warn("Proof does not contain any identifiers event will not be persisted"); } @@ -110,7 +119,7 @@ private void handleAckedOrVerified(PresentationExchangeRecord proof) { /** * Handles all proof request - * + * * @param proof {@link PresentationExchangeRecord} */ private void handleProofRequest(@NonNull PresentationExchangeRecord proof) { @@ -135,13 +144,17 @@ private void handleProofRequest(@NonNull PresentationExchangeRecord proof) { final PartnerProof pp = defaultProof(p.getId(), proof) .setProofRequest(proof.getPresentationRequest()); pProofRepo.save(pp); + eventPublisher.publishEvent(PresentationRequestReceivedEvent.builder() + .partnerProof(pp) + .build()); + })); } /** * Handle present proof problem report message - * + * * @param threadId the thread id of the exchange * @param description the problem description */ @@ -151,7 +164,7 @@ void handleProblemReport(@NonNull String threadId, @NonNull String description) /** * Build db proof representation with all mandatory fields that are required - * + * * @param partnerId the partner id * @param proof {link PresentationExchangeRecord} * @return {@link PartnerProof} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/CredentialAddedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/CredentialAddedEvent.java new file mode 100644 index 000000000..ec0bb2773 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/CredentialAddedEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.bpa.api.aries.AriesCredential; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class CredentialAddedEvent { + + private AriesCredential credential; + private V1CredentialExchange credentialExchange; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java new file mode 100644 index 000000000..464a77bff --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.api.PartnerAPI; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PartnerAddedEvent { + + private PartnerAPI partner; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java new file mode 100644 index 000000000..7d2e71444 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.api.PartnerAPI; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PartnerRemovedEvent { + + private PartnerAPI partner; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java new file mode 100644 index 000000000..b626fb9b3 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.api.PartnerAPI; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PartnerRequestReceivedEvent { + + private PartnerAPI partner; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java new file mode 100644 index 000000000..b5614068e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.PartnerProof; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PresentationRequestCompletedEvent { + + private PartnerProof partnerProof; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java new file mode 100644 index 000000000..1d919d5f8 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.PartnerProof; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PresentationRequestReceivedEvent { + + private PartnerProof partnerProof; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java new file mode 100644 index 000000000..d42e6a5d5 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "activity_vw") +public class Activity { + + @Id + private UUID id; + + @OneToOne + private Partner partner; + + private String type; + private String role; + private String state; + + private Instant updatedAt; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java new file mode 100644 index 000000000..44038680e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.repository; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; +import org.hyperledger.bpa.model.Activity; + +import java.util.List; +import java.util.UUID; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface ActivityRepository extends GenericRepository { + + // @Join and @Query do not work together + // Need @Query to select in 2 lists, but since not possible? let's take + // advantage of the join to not have N+1 queries to get the partner + // Will have to process the result set to match the states we want... + // @Query("SELECT act.* FROM activity_vw as act where act.type in (:types) and + // act.state in (:states)") + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + Iterable findByTypeIn(@NonNull List types); + +} diff --git a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql new file mode 100644 index 000000000..e507ec95d --- /dev/null +++ b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql @@ -0,0 +1,10 @@ +create or replace view activity_vw as +select t.* from +(select p.id as partner_id, 'CONNECTION_REQUEST' as type, p.id, p.state, case when p.incoming then 'CONNECTION_REQUEST_RECIPIENT' else 'CONNECTION_REQUEST_SENDER' end as role, p.updated_at from partner as p +union +select pp.partner_id, 'PRESENTATION_EXCHANGE' as type, pp.id, pp.state, pp.role, case when pp.issued_at is not null then pp.issued_at else pp.created_at end as updated_at from partner_proof as pp +union +select bce.partner_id, 'CREDENTIAL_EXCHANGE' as type, bce.id, bce.state, bce.role, bce.updated_at from bpa_credential_exchange as bce +union +select mcp.id as partner_id, 'CREDENTIAL_EXCHANGE' as type, mc.id, mc.state, 'HOLDER' as role, mc.issued_at as updated_at from my_credential as mc inner join partner as mcp on mc.connection_id = mcp.connection_id) as t +order by t.updated_at desc; diff --git a/backend/business-partner-agent/src/main/resources/log4j2.xml b/backend/business-partner-agent/src/main/resources/log4j2.xml index a42304076..4372838c3 100644 --- a/backend/business-partner-agent/src/main/resources/log4j2.xml +++ b/backend/business-partner-agent/src/main/resources/log4j2.xml @@ -6,12 +6,12 @@ - + - + From cc85b6799eceb44569af27fac4878fee1399b16f Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Mon, 26 Jul 2021 17:36:22 -0700 Subject: [PATCH 2/8] no message Signed-off-by: Jason Sherman --- .../controller/api/WebSocketMessageBody.java | 26 ++- .../bpa/impl/NotificationEventListener.java | 92 +++++++-- .../bpa/impl/aries/ProofEventHandler.java | 8 +- .../bpa/impl/aries/ProofManager.java | 16 +- .../PresentationRequestCompletedEvent.java | 4 +- .../PresentationRequestReceivedEvent.java | 4 +- .../hyperledger/bpa/impl/util/Converter.java | 12 ++ frontend/src/main.js | 4 +- frontend/src/store/index.js | 4 +- frontend/src/store/modules/notifications.js | 183 ++++++++++++++++++ 10 files changed, 318 insertions(+), 35 deletions(-) create mode 100644 frontend/src/store/modules/notifications.js diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java index 76059201b..f449f1fa0 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java @@ -17,6 +17,7 @@ */ package org.hyperledger.bpa.controller.api; +import io.micronaut.core.annotation.Nullable; import lombok.*; import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.api.aries.AriesCredential; @@ -46,6 +47,7 @@ public static final class WebSocketMessage { private WebSocketMessageState state; private String linkId; private Object info; + private PartnerAPI partner; } public enum WebSocketMessageType { @@ -54,7 +56,15 @@ public enum WebSocketMessageType { PARTNER, PROOF, PROOFREQUEST, - NOTIFICATION + NOTIFICATION, + onCredentialAdded, + onPartnerRequestReceived, + onPartnerAdded, + onPartnerRemoved, + onPresentationVerified, + onPresentationProved, + onPresentationRequestReceived, + onNewTask } public enum WebSocketMessageState { @@ -107,4 +117,18 @@ public static WebSocketMessageBody notification(Object info, Boolean completed) .build()); } + public static WebSocketMessageBody notificationEvent(@NonNull WebSocketMessageType type, + @Nullable String linkId, + @Nullable Object info, + @Nullable PartnerAPI partner) { + return WebSocketMessageBody.of(WebSocketMessage + .builder() + .type(type) + .state(WebSocketMessageState.SENT) + .info(info) + .partner(partner) + .linkId(linkId) + .build()); + } + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java index 3728fc745..d5fb80b72 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java @@ -20,6 +20,9 @@ import io.micronaut.runtime.event.annotation.EventListener; import io.micronaut.scheduling.annotation.Async; import lombok.extern.slf4j.Slf4j; +import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; +import org.hyperledger.bpa.config.ActivityLogConfig; +import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.notification.*; import javax.inject.Inject; @@ -32,6 +35,12 @@ public class NotificationEventListener { @Inject PartnerManager partnerManager; + @Inject + MessageService messageService; + + @Inject + ActivityLogConfig activityLogConfig; + @EventListener @Async public void onCredentialAddedEvent(CredentialAddedEvent event) { @@ -39,7 +48,20 @@ public void onCredentialAddedEvent(CredentialAddedEvent event) { // we have the connection id, but not the partner, will need to look up // partner... partnerManager.getPartnerByConnectionId(event.getCredential().getConnectionId()).ifPresent(p -> { - log.debug(p.getDid()); + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onCredentialAdded, + event.getCredential().getId().toString(), + event.getCredential(), + p); + messageService.sendMessage(message); + + WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onNewTask, + event.getCredential().getId().toString(), + event.getCredential(), + p); + messageService.sendMessage(task); + }); } @@ -47,21 +69,39 @@ public void onCredentialAddedEvent(CredentialAddedEvent event) { @Async public void onPartnerRequestReceivedEvent(PartnerRequestReceivedEvent event) { log.debug("onPartnerRequestReceivedEvent"); - // we have the partner + // only notify if this is a task (requires manual intervention) + if (activityLogConfig.getConnectionStatesForTasks().contains(event.getPartner().getState())) { + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPartnerRequestReceived, + event.getPartner().getId(), + null, + event.getPartner()); + messageService.sendMessage(message); + } } @EventListener @Async public void onPartnerAddedEvent(PartnerAddedEvent event) { log.debug("onPartnerAddedEvent"); - // we have the partner + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPartnerAdded, + event.getPartner().getId(), + null, + event.getPartner()); + messageService.sendMessage(message); } @EventListener @Async public void onPartnerRemovedEvent(PartnerRemovedEvent event) { log.debug("onPartnerRemovedEvent"); - // we have the partner + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPartnerRemoved, + event.getPartner().getId(), + null, + event.getPartner()); + messageService.sendMessage(message); } @EventListener @@ -69,9 +109,22 @@ public void onPartnerRemovedEvent(PartnerRemovedEvent event) { public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEvent event) { log.debug("onPresentationRequestCompletedEvent"); // we have the partner id, but not the partner, will need to look up partner... - partnerManager.getPartnerById(event.getPartnerProof().getPartnerId()).ifPresent(p -> { - log.debug(p.getDid()); - // send out separate notifications for verified or received. + partnerManager.getPartnerById(event.getProofExchange().getPartnerId()).ifPresent(p -> { + WebSocketMessageBody message = null; + if (event.getProofExchange().getRole().equals(PresentationExchangeRole.PROVER)) { + message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPresentationProved, + event.getProofExchange().getId().toString(), + event.getProofExchange(), + p); + } else { + message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPresentationVerified, + event.getProofExchange().getId().toString(), + event.getProofExchange(), + p); + } + messageService.sendMessage(message); }); } @@ -79,11 +132,24 @@ public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEven @Async public void onPresentationRequestReceivedEvent(PresentationRequestReceivedEvent event) { log.debug("onPresentationRequestReceivedEvent"); - // we have the partner id, but not the partner, will need to look up partner... - partnerManager.getPartnerById(event.getPartnerProof().getPartnerId()).ifPresent(p -> { - log.debug(p.getDid()); - }); - } + // only notify if this is a task (requires manual intervention) + if (activityLogConfig.getPresentationExchangeStatesForTasks().contains(event.getProofExchange().getState())) { + // we have the partner id, but not the partner, will need to look up partner... + partnerManager.getPartnerById(event.getProofExchange().getPartnerId()).ifPresent(p -> { + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPresentationRequestReceived, + event.getProofExchange().getId().toString(), + event.getProofExchange(), + p); + messageService.sendMessage(message); - // send message over socket... + WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onNewTask, + event.getProofExchange().getId().toString(), + event.getProofExchange(), + p); + messageService.sendMessage(task); + }); + } + } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java index 1071408a9..dbdf4c996 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java @@ -29,6 +29,7 @@ import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.notification.PresentationRequestCompletedEvent; import org.hyperledger.bpa.impl.notification.PresentationRequestReceivedEvent; +import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.PartnerProof; import org.hyperledger.bpa.repository.PartnerProofRepository; import org.hyperledger.bpa.repository.PartnerRepository; @@ -53,6 +54,9 @@ public class ProofEventHandler { @Inject MessageService messageService; + @Inject + Converter conv; + @Inject ApplicationEventPublisher eventPublisher; @@ -109,7 +113,7 @@ private void handleAckedOrVerified(PresentationExchangeRecord proof) { WebSocketMessageBody.WebSocketMessageType.PROOF, savedProof); eventPublisher.publishEvent(PresentationRequestCompletedEvent.builder() - .partnerProof(savedProof) + .proofExchange(conv.toAPIObject(savedProof)) .build()); } else { log.warn("Proof does not contain any identifiers event will not be persisted"); @@ -145,7 +149,7 @@ private void handleProofRequest(@NonNull PresentationExchangeRecord proof) { .setProofRequest(proof.getPresentationRequest()); pProofRepo.save(pp); eventPublisher.publishEvent(PresentationRequestReceivedEvent.builder() - .partnerProof(pp) + .proofExchange(conv.toAPIObject(pp)) .build()); })); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java index 1d59b69ee..89a5e1a48 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java @@ -17,7 +17,6 @@ */ package org.hyperledger.bpa.impl.aries; -import com.fasterxml.jackson.databind.JsonNode; import io.micronaut.context.annotation.Value; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.CollectionUtils; @@ -251,7 +250,7 @@ public void sendProofProposal(@NonNull UUID partnerId, @NonNull UUID myCredentia public List listPartnerProofs(@NonNull UUID partnerId) { List result = new ArrayList<>(); - pProofRepo.findByPartnerIdOrderByRole(partnerId).forEach(p -> result.add(toApiProof(p))); + pProofRepo.findByPartnerIdOrderByRole(partnerId).forEach(p -> result.add(conv.toAPIObject(p))); return result; } @@ -259,7 +258,7 @@ public Optional getPartnerProofById(@NonNull UUID id) { Optional result = Optional.empty(); final Optional proof = pProofRepo.findById(id); if (proof.isPresent()) { - result = Optional.of(toApiProof(proof.get())); + result = Optional.of(conv.toAPIObject(proof.get())); } return result; } @@ -285,7 +284,7 @@ void sendMessage( @NonNull WebSocketMessageBody.WebSocketMessageState state, @NonNull WebSocketMessageBody.WebSocketMessageType type, @NonNull PartnerProof pp) { - messageService.sendMessage(WebSocketMessageBody.proof(state, type, toApiProof(pp))); + messageService.sendMessage(WebSocketMessageBody.proof(state, type, conv.toAPIObject(pp))); } private @Nullable String resolveIssuer(String credDefId) { @@ -296,15 +295,6 @@ void sendMessage( return issuer; } - private AriesProofExchange toApiProof(@NonNull PartnerProof p) { - AriesProofExchange proof = AriesProofExchange.from(p, - p.getProof() != null ? conv.fromMap(p.getProof(), JsonNode.class) : null); - if (StringUtils.isNotEmpty(p.getSchemaId())) { - proof.setTypeLabel(schemaService.getSchemaLabel(p.getSchemaId())); - } - return proof; - } - private void sendPresentProofProblemReport(@NonNull String PresentationExchangeId, @NonNull String problemString) throws IOException { V10PresentationProblemReportRequest request = V10PresentationProblemReportRequest.builder() diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java index b5614068e..7bfab202c 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.model.PartnerProof; +import org.hyperledger.bpa.api.aries.AriesProofExchange; @Builder @NoArgsConstructor @@ -29,6 +29,6 @@ @Getter public class PresentationRequestCompletedEvent { - private PartnerProof partnerProof; + private AriesProofExchange proofExchange; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java index 1d919d5f8..8c41cbf31 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.model.PartnerProof; +import org.hyperledger.bpa.api.aries.AriesProofExchange; @Builder @NoArgsConstructor @@ -29,6 +29,6 @@ @Getter public class PresentationRequestReceivedEvent { - private PartnerProof partnerProof; + private AriesProofExchange proofExchange; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java index a0dddf02d..bfd4e7085 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java @@ -40,11 +40,14 @@ import org.hyperledger.bpa.api.MyDocumentAPI; import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.api.PartnerAPI.PartnerCredential; +import org.hyperledger.bpa.api.aries.AriesProofExchange; import org.hyperledger.bpa.impl.aries.config.SchemaService; import org.hyperledger.bpa.model.MyDocument; import org.hyperledger.bpa.model.Partner; import io.micronaut.core.annotation.Nullable; +import org.hyperledger.bpa.model.PartnerProof; + import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.constraints.NotNull; @@ -193,6 +196,15 @@ public Optional writeValueAsString(Object value) { return Optional.empty(); } + public AriesProofExchange toAPIObject(@NonNull PartnerProof p) { + AriesProofExchange proof = AriesProofExchange.from(p, + p.getProof() != null ? this.fromMap(p.getProof(), JsonNode.class) : null); + if (io.micronaut.core.util.StringUtils.isNotEmpty(p.getSchemaId())) { + proof.setTypeLabel(schemaService.getSchemaLabel(p.getSchemaId())); + } + return proof; + } + private String resolveTypeLabel(@NonNull CredentialType type, @Nullable String schemaId) { String result = null; if (CredentialType.SCHEMA_BASED.equals(type) diff --git a/frontend/src/main.js b/frontend/src/main.js index 35f8cf451..936b76d12 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -76,7 +76,9 @@ Vue.use(VueNativeSock, socketApi, { case "NOTIFICATION": target = "notification"; break; - + default: + target = "onNotification"; + break; } } } diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 246393570..0b8652b97 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -2,7 +2,7 @@ Copyright (c) 2020 - for information on the respective copyright owner see the NOTICE file and/or the repository at https://github.com/hyperledger-labs/organizational-agent - + SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ import taa from "./modules/taa"; import socketEvents from "./modules/socketevents"; import * as actions from "./actions"; import * as getters from "./getters"; +import notifications from "@/store/modules/notifications"; Vue.use(Vuex); @@ -59,6 +60,7 @@ const store = new Vuex.Store({ modules: { taa, socketEvents, + notifications, }, }); diff --git a/frontend/src/store/modules/notifications.js b/frontend/src/store/modules/notifications.js new file mode 100644 index 000000000..a6331a112 --- /dev/null +++ b/frontend/src/store/modules/notifications.js @@ -0,0 +1,183 @@ +const state = { + credentialsAdded: {}, + partnerRequestsReceived: {}, + partnersAdded: {}, + partnersRemoved: {}, + presentationsProved: {}, + presentationsVerified: {}, + presentationRequestsReceived: {}, + tasksAdded: {} +}; + +const getters = { + credentialsAddedCount: (state) => { + return Object.keys(state.credentialsAdded).length; + }, + credentialsAdded: (state) => { + return state.credentialsAdded; + }, + partnerRequestsReceivedCount: (state) => { + return Object.keys(state.partnerRequestsReceived).length; + }, + partnerRequestsReceived: (state) => { + return state.partnerRequestsReceived; + }, + partnersAddedCount: (state) => { + return Object.keys(state.partnersAdded).length; + }, + partnersAdded: (state) => { + return state.partnersAdded; + }, + partnersRemovedCount: (state) => { + return Object.keys(state.partnersRemoved).length; + }, + partnersRemoved: (state) => { + return state.partnersRemoved; + }, + presentationsProvedCount: (state) => { + return Object.keys(state.presentationsProved).length; + }, + presentationsProved: (state) => { + return state.presentationsProved; + }, + presentationsVerifiedCount: (state) => { + return Object.keys(state.presentationsVerified).length; + }, + presentationsVerified: (state) => { + return state.presentationsVerified; + }, + presentationRequestsReceivedCount: (state) => { + return Object.keys(state.presentationRequestsReceived).length; + }, + presentationRequestsReceived: (state) => { + return state.presentationRequestsReceived; + }, + tasksAddedCount: (state) => { + return Object.keys(state.tasksAdded).length; + }, + tasksAdded: (state) => { + return state.tasksAdded; + }, +}; + +const mutations = { + onNotification(state, payload) { + let type = payload.message.type; + let id = payload.message.linkId; + switch (type) { + case "onCredentialAdded": + state.credentialsAdded = { ...state.credentialsAdded, [id]: payload }; + break; + case "onPartnerRequestReceived": + state.partnerRequestsReceived = { ...state.partnerRequestsReceived, [id]: payload }; + break; + case "onPartnerAdded": + state.partnersAdded = { ...state.partnersAdded, [id]: payload }; + break; + case "onPartnerRemoved": + state.partnersRemoved = { ...state.partnersRemoved, [id]: payload }; + break; + case "onPresentationProved": + state.presentationsProved = { ...state.presentationsProved, [id]: payload }; + break; + case "onPresentationVerified": + state.presentationsVerified = { ...state.presentationsVerified, [id]: payload }; + break; + case "onPresentationRequestReceived": + state.presentationRequestsReceived = { ...state.presentationRequestsReceived, [id]: payload }; + break; + case "onNewTask": + state.tasksAdded = { ...state.tasksAdded, [id]: payload }; + break; + default: + console.log(`Unknown notification type: ${type}`); + } + }, +}; + +const actions = { + credentialsAddedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.credentialsAdded, id)) { + const tmp = { ...state.credentialsAdded }; + delete tmp[id]; + state.credentialsAdded = tmp; + } + }, + partnerRequestsReceivedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.partnerRequestsReceived, id)) { + const tmp = { ...state.partnerRequestsReceived }; + delete tmp[id]; + state.partnerRequestsReceived = tmp; + } + }, + partnersAddedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.partnersAdded, id)) { + const tmp = { ...state.partnersAdded }; + delete tmp[id]; + state.partnersAdded = tmp; + } + }, + partnersRemovedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.partnersRemoved, id)) { + const tmp = { ...state.partnersRemoved }; + delete tmp[id]; + state.partnersRemoved = tmp; + } + }, + presentationsReceivedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.presentationsReceived, id)) { + const tmp = { ...state.presentationsReceived }; + delete tmp[id]; + state.presentationsReceived = tmp; + } + }, + presentationsVerifiedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.presentationsVerified, id)) { + const tmp = { ...state.presentationsVerified }; + delete tmp[id]; + state.presentationsVerified = tmp; + } + }, + presentationRequestsReceivedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.presentationRequestsReceived, id)) { + const tmp = { ...state.presentationRequestsReceived }; + delete tmp[id]; + state.presentationRequestsReceived = tmp; + } + }, + tasksAddedSeen(state, payload) { + let id = payload.id; + if ({}.hasOwnProperty.call(state.tasksAdded, id)) { + const tmp = { ...state.tasksAdded }; + delete tmp[id]; + state.tasksAdded = tmp; + } + }, + clearTasksAdded() { + state.tasksAdded = {}; + }, + clearAllNotifications() { + state.tasksAdded = {}; + state.presentationRequestsReceived = {}; + state.presentationsVerified = {}; + state.presentationsReceived = {}; + state.partnersRemoved = {}; + state.partnersAdded = {}; + state.partnerRequestsReceived = {}; + state.credentialsAdded = {}; + } +}; + +export default { + state, + getters, + actions, + mutations, +}; From c4a0e7e2a697f13b2b2da61afaa1936fd36a2307 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Wed, 28 Jul 2021 16:03:44 -0700 Subject: [PATCH 3/8] phew! change activity list is persistent, it is populated from events from managers. Signed-off-by: Jason Sherman --- .../bpa/config/acapy/AcaPyAuthFetcher.java | 4 +- .../acapy/AcaPyAuthFetcherAllowAll.java | 3 +- .../bpa/controller/ActivitiesController.java | 6 +- .../controller/api/WebSocketMessageBody.java | 2 + .../controller/api/activity/ActivityItem.java | 2 +- .../controller/api/activity/ActivityRole.java | 5 - .../api/activity/ActivityState.java | 8 +- .../bpa/impl/ActivitiesEventHandler.java | 72 ++--- .../bpa/impl/ActivitiesManager.java | 192 ------------ .../hyperledger/bpa/impl/ActivityManager.java | 280 ++++++++++++++++++ .../bpa/impl/NotificationEventListener.java | 131 ++++++-- .../hyperledger/bpa/impl/PartnerManager.java | 4 - .../bpa/impl/aries/ConnectionManager.java | 25 +- .../bpa/impl/aries/CredentialManager.java | 2 +- .../bpa/impl/aries/ProofEventHandler.java | 11 +- .../bpa/impl/aries/ProofManager.java | 20 ++ .../notification/PartnerAcceptedEvent.java | 33 +++ .../impl/notification/PartnerAddedEvent.java | 4 +- .../notification/PartnerRemovedEvent.java | 4 +- .../PartnerRequestCompletedEvent.java | 34 +++ .../PartnerRequestReceivedEvent.java | 4 +- .../PresentationRequestCompletedEvent.java | 4 +- .../PresentationRequestDeclinedEvent.java | 34 +++ .../PresentationRequestDeletedEvent.java | 34 +++ .../PresentationRequestReceivedEvent.java | 4 +- .../PresentationRequestSentEvent.java | 34 +++ .../org/hyperledger/bpa/model/Activity.java | 29 +- .../bpa/repository/ActivityRepository.java | 49 ++- .../V1.17__add-activity-table.sql | 11 + .../src/main/resources/log4j2.xml | 2 +- frontend/src/components/ActivityList.vue | 45 ++- frontend/src/constants.js | 8 +- frontend/src/locales/bcgov.json | 1 + frontend/src/locales/en.json | 1 + 34 files changed, 756 insertions(+), 346 deletions(-) delete mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAcceptedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestCompletedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeclinedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeletedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestSentEvent.java create mode 100644 backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-table.sql diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcher.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcher.java index 5816df499..cc1eef36f 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcher.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcher.java @@ -42,8 +42,8 @@ * HTTP header. As this is not something micronaut handles out of the box, a * custom {@link AuthenticationFetcher} needs to be provided. * - * The AuthFetcher becomes active if either the environment variable BPA_WEBHOOK_KEY - * or the system property bpa.webhook.key is set. + * The AuthFetcher becomes active if either the environment variable + * BPA_WEBHOOK_KEY or the system property bpa.webhook.key is set. */ @Slf4j @Singleton diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcherAllowAll.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcherAllowAll.java index ab1cfae53..adc152470 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcherAllowAll.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/config/acapy/AcaPyAuthFetcherAllowAll.java @@ -29,7 +29,8 @@ import javax.inject.Singleton; /** - * Backwards compatible AuthFetcher if security is set to true, but no properties are set. + * Backwards compatible AuthFetcher if security is set to true, but no + * properties are set. */ @Slf4j @Singleton diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ActivitiesController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ActivitiesController.java index 2c96c7838..d74aa8e14 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ActivitiesController.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/ActivitiesController.java @@ -28,7 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.hyperledger.bpa.controller.api.activity.ActivityItem; import org.hyperledger.bpa.controller.api.activity.ActivitySearchParameters; -import org.hyperledger.bpa.impl.ActivitiesManager; +import org.hyperledger.bpa.impl.ActivityManager; import javax.inject.Inject; import javax.validation.Valid; @@ -41,7 +41,7 @@ public class ActivitiesController { @Inject - ActivitiesManager activitiesManager; + ActivityManager activityManager; /** * List Items, if no filters return all @@ -51,7 +51,7 @@ public class ActivitiesController { */ @Get public HttpResponse> listActivities(@RequestBean @Valid ActivitySearchParameters parameters) { - return HttpResponse.ok(activitiesManager.getItems(parameters)); + return HttpResponse.ok(activityManager.getItems(parameters)); } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java index cc52fffac..fbbd00d75 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java @@ -62,10 +62,12 @@ public enum WebSocketMessageType { onCredentialAdded, onPartnerRequestReceived, onPartnerAdded, + onPartnerAccepted, onPartnerRemoved, onPresentationVerified, onPresentationProved, onPresentationRequestReceived, + onPresentationRequestSent, onNewTask } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java index d66cb0556..cfd08c644 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java @@ -35,5 +35,5 @@ public class ActivityItem { private Long updatedAt; private String linkId; private PartnerAPI partner; - private Boolean task; + private Boolean completed; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java index 7c07338a1..c0b80df4c 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityRole.java @@ -30,9 +30,4 @@ public enum ActivityRole { PRESENTATION_EXCHANGE_PROVER, @JsonProperty("presentation_exchange_verifier") PRESENTATION_EXCHANGE_VERIFIER, - @JsonProperty("credential_exchange_holder") - CREDENTIAL_EXCHANGE_HOLDER, - @JsonProperty("credential_exchange_prover") - CREDENTIAL_EXCHANGE_ISSUER, - } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java index b32237752..77d8e1701 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityState.java @@ -34,11 +34,7 @@ public enum ActivityState { PRESENTATION_EXCHANGE_RECEIVED, @JsonProperty("presentation_exchange_accepted") PRESENTATION_EXCHANGE_ACCEPTED, - @JsonProperty("credential_exchange_sent") - CREDENTIAL_EXCHANGE_SENT, - @JsonProperty("credential_exchange_received") - CREDENTIAL_EXCHANGE_RECEIVED, - @JsonProperty("credential_exchange_accepted") - CREDENTIAL_EXCHANGE_ACCEPTED + @JsonProperty("presentation_exchange_declined") + PRESENTATION_EXCHANGE_DECLINED, } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java index 68d556352..35fc69db5 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java @@ -19,14 +19,8 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.hyperledger.aries.api.connection.ConnectionRecord; -import org.hyperledger.aries.api.message.BasicMessage; -import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; import org.hyperledger.aries.webhook.EventHandler; -import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.config.ActivityLogConfig; -import org.hyperledger.bpa.controller.api.WebSocketMessageBody; -import org.hyperledger.bpa.controller.api.activity.ActivityType; import javax.inject.Inject; import javax.inject.Singleton; @@ -43,44 +37,30 @@ public class ActivitiesEventHandler extends EventHandler { @Inject PartnerManager partnerManager; - - @Override - public void handleConnection(ConnectionRecord connection) { - Boolean completed = null; - boolean notify = true; - if (activityLogConfig.getConnectionStatesForTasks().contains(connection.getState())) { - completed = false; - // we do not always want to notify... it is only a task on incoming request - notify = connection.isIncomingConnection(); - } else if (activityLogConfig.getConnectionStatesCompleted().contains(connection.getState())) { - completed = true; - } - if (completed != null && notify) { - messageService - .sendMessage(WebSocketMessageBody.notification(ActivityType.CONNECTION_REQUEST, completed)); - } - } - - @Override - public void handleProof(PresentationExchangeRecord proof) { - Boolean completed = null; - if (activityLogConfig.getPresentationExchangeStatesForTasks().contains(proof.getState())) { - completed = false; - } else if (activityLogConfig.getPresentationExchangeStatesCompleted().contains(proof.getState())) { - completed = true; - } - if (completed != null) { - messageService - .sendMessage(WebSocketMessageBody.notification(ActivityType.PRESENTATION_EXCHANGE, completed)); - } - } - - @Override - public void handleBasicMessage(BasicMessage message) { - PartnerAPI partner = partnerManager.getPartnerByConnectionId(message.getConnectionId()); - if (partner != null) { - messageService.sendMessage(WebSocketMessageBody.message(partner, message)); - } - } - + /* + * @Override public void handleConnection(ConnectionRecord connection) { Boolean + * completed = null; boolean notify = true; if + * (activityLogConfig.getConnectionStatesForTasks().contains(connection.getState + * ())) { completed = false; // we do not always want to notify... it is only a + * task on incoming request notify = connection.isIncomingConnection(); } else + * if (activityLogConfig.getConnectionStatesCompleted().contains(connection. + * getState())) { completed = true; } if (completed != null && notify) { + * messageService .sendMessage(WebSocketMessageBody.notification(ActivityType. + * CONNECTION_REQUEST, completed)); } } + * + * @Override public void handleProof(PresentationExchangeRecord proof) { Boolean + * completed = null; if + * (activityLogConfig.getPresentationExchangeStatesForTasks().contains(proof. + * getState())) { completed = false; } else if + * (activityLogConfig.getPresentationExchangeStatesCompleted().contains(proof. + * getState())) { completed = true; } if (completed != null) { messageService + * .sendMessage(WebSocketMessageBody.notification(ActivityType. + * PRESENTATION_EXCHANGE, completed)); } } + * + * @Override public void handleBasicMessage(BasicMessage message) { PartnerAPI + * partner = partnerManager.getPartnerByConnectionId(message.getConnectionId()); + * if (partner != null) { + * messageService.sendMessage(WebSocketMessageBody.message(partner, message)); } + * } + */ } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java deleted file mode 100644 index 988516091..000000000 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesManager.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2020-2021 - for information on the respective copyright owner - * see the NOTICE file and/or the repository at - * https://github.com/hyperledger-labs/business-partner-agent - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.hyperledger.bpa.impl; - -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.hyperledger.aries.api.connection.ConnectionState; -import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; -import org.hyperledger.aries.api.present_proof.PresentationExchangeState; -import org.hyperledger.bpa.api.PartnerAPI; -import org.hyperledger.bpa.config.ActivityLogConfig; -import org.hyperledger.bpa.controller.api.activity.*; -import org.hyperledger.bpa.impl.util.Converter; -import org.hyperledger.bpa.model.Activity; -import org.hyperledger.bpa.repository.ActivityRepository; -import org.hyperledger.bpa.repository.PartnerProofRepository; -import org.hyperledger.bpa.repository.PartnerRepository; - -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@NoArgsConstructor -public class ActivitiesManager { - - @Inject - PartnerRepository partnerRepo; - - @Inject - PartnerProofRepository proofRepository; - - @Inject - ActivityLogConfig activityLogConfig; - - @Inject - ActivityRepository activityRepository; - - @Inject - Converter converter; - - public List getItems(ActivitySearchParameters parameters) { - List results = new ArrayList<>(); - List types = getSearchTypes(parameters); - List states = getSearchStates(parameters); - Iterable activities = activityRepository.findByTypeIn(types); - activities.forEach(activity -> { - // filter based on the appropriate states. - // we can't add states to the query without losing the auto population of the - // partner.. - if (states.contains(activity.getState())) { - PartnerAPI partner = converter.toAPIObject(activity.getPartner()); - // for now, just use the partner as the link.. - ActivityItem item = ActivityItem.builder() - .linkId(partner.getId()) - .partner(partner) - .updatedAt(activity.getUpdatedAt().toEpochMilli()) - .type(getActivityType(activity)) - .role(getActivityRole(activity)) - .state(getActivityState(activity)) - .task(isTask(activity)) - .build(); - results.add(item); - } - }); - return results; - } - - private ActivityRole getActivityRole(Activity a) { - switch (ActivityType.valueOf(a.getType())) { - case CONNECTION_REQUEST: - return ActivityRole.valueOf(a.getRole()); - case PRESENTATION_EXCHANGE: - default: - PresentationExchangeRole per = PresentationExchangeRole.valueOf(a.getRole()); - switch (per) { - case VERIFIER: - return ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; - case PROVER: - default: - return ActivityRole.PRESENTATION_EXCHANGE_PROVER; - } - } - } - - private ActivityState getActivityState(Activity a) { - switch (ActivityType.valueOf(a.getType())) { - case CONNECTION_REQUEST: - ConnectionState cs = ConnectionState.valueOf(a.getState()); - if (activityLogConfig.getConnectionStatesCompleted().contains(cs)) { - return ActivityState.CONNECTION_REQUEST_ACCEPTED; - } else { - if (ActivityRole.CONNECTION_REQUEST_SENDER.equals(ActivityRole.valueOf(a.getRole()))) { - return ActivityState.CONNECTION_REQUEST_SENT; - } else { - return ActivityState.CONNECTION_REQUEST_RECEIVED; - } - } - case PRESENTATION_EXCHANGE: - default: - PresentationExchangeState pes = PresentationExchangeState.valueOf(a.getState()); - switch (pes) { - case VERIFIED: - case PRESENTATION_ACKED: - return ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; - case REQUEST_SENT: - case PRESENTATIONS_SENT: - return ActivityState.PRESENTATION_EXCHANGE_SENT; - case REQUEST_RECEIVED: - case PRESENTATION_RECEIVED: - return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; - default: - PresentationExchangeRole per = PresentationExchangeRole.valueOf(a.getRole()); - switch (per) { - case VERIFIER: - return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; - case PROVER: - default: - return ActivityState.PRESENTATION_EXCHANGE_SENT; - } - } - } - } - - private ActivityType getActivityType(Activity a) { - switch (ActivityType.valueOf(a.getType())) { - case CONNECTION_REQUEST: - return ActivityType.CONNECTION_REQUEST; - case PRESENTATION_EXCHANGE: - default: - return ActivityType.PRESENTATION_EXCHANGE; - } - } - - private boolean isTask(Activity a) { - switch (ActivityType.valueOf(a.getType())) { - case CONNECTION_REQUEST: - return activityLogConfig.getConnectionStatesForTasks().contains(ConnectionState.valueOf(a.getState())); - case PRESENTATION_EXCHANGE: - return activityLogConfig.getPresentationExchangeStatesForTasks() - .contains(PresentationExchangeState.valueOf(a.getState())); - default: - return false; - } - } - - private List getSearchTypes(ActivitySearchParameters parameters) { - if (parameters.getType() != null) { - return List.of(parameters.getType().toString()); - } - // TODO: add credential offers in when we support it - return List.of(ActivityType.PRESENTATION_EXCHANGE.toString(), ActivityType.CONNECTION_REQUEST.toString()); - } - - private List getSearchStates(ActivitySearchParameters parameters) { - List states = new ArrayList<>(); - if (parameters.getActivity()) { - states.addAll(activityLogConfig.getConnectionStatesForActivities().stream() - .map(v -> v.toString()) - .collect(Collectors.toList())); - states.addAll(activityLogConfig.getPresentationExchangeStatesForActivities().stream() - .map(v -> v.toString()) - .collect(Collectors.toList())); - } - if (parameters.getTask()) { - states.addAll(activityLogConfig.getConnectionStatesForTasks().stream() - .map(v -> v.toString()) - .collect(Collectors.toList())); - states.addAll(activityLogConfig.getPresentationExchangeStatesForTasks().stream() - .map(v -> v.toString()) - .collect(Collectors.toList())); - } - return states; - } - -} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java new file mode 100644 index 000000000..b1976270e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl; + +import io.micronaut.core.annotation.NonNull; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; +import org.hyperledger.bpa.config.ActivityLogConfig; +import org.hyperledger.bpa.controller.api.activity.*; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.model.Activity; +import org.hyperledger.bpa.model.Partner; +import org.hyperledger.bpa.model.PartnerProof; +import org.hyperledger.bpa.repository.ActivityRepository; +import org.hyperledger.bpa.repository.PartnerProofRepository; +import org.hyperledger.bpa.repository.PartnerRepository; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@NoArgsConstructor +public class ActivityManager { + + @Inject + PartnerRepository partnerRepo; + + @Inject + PartnerProofRepository proofRepository; + + @Inject + ActivityLogConfig activityLogConfig; + + @Inject + ActivityRepository activityRepository; + + @Inject + Converter converter; + + public List getItems(ActivitySearchParameters parameters) { + List activities = new ArrayList<>(); + + if (parameters.getActivity() && parameters.getTask()) { + if (parameters.getType() != null) { + activities = activityRepository.findByTypeOrderByUpdatedAt(parameters.getType()); + } else { + activities = activityRepository.listOrderByUpdatedAtDesc(); + } + } else if (parameters.getTask()) { + if (parameters.getType() != null) { + activities = activityRepository + .findByTypeAndCompletedFalseOrderByUpdatedAtDesc(parameters.getType()); + } else { + activities = activityRepository.findByCompletedFalseOrderByUpdatedAtDesc(); + } + } else { + if (parameters.getType() != null) { + activities = activityRepository + .findByTypeAndCompletedTrueOrderByUpdatedAtDesc(parameters.getType()); + } else { + activities = activityRepository.findByCompletedTrueOrderByUpdatedAtDesc(); + } + } + + return activities.stream().map(this::convert).collect(Collectors.toList()); + } + + public void addPartnerRequestReceivedTask(@NonNull Partner partner) { + // in case event is fired multiple times + Optional existing = activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), + ActivityType.CONNECTION_REQUEST, + ActivityRole.CONNECTION_REQUEST_RECIPIENT); + if (!existing.isPresent()) { + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_RECIPIENT) + .state(ActivityState.CONNECTION_REQUEST_RECEIVED) + .completed(false) + .build(); + activityRepository.save(a); + } + } + + public void completePartnerRequestTask(@NonNull Partner partner) { + activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), + ActivityType.CONNECTION_REQUEST, + ActivityRole.CONNECTION_REQUEST_RECIPIENT).ifPresentOrElse(activity -> { + // set to completed and mark accepted + activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); + activity.setCompleted(true); + activityRepository.update(activity); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_RECIPIENT) + .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) + .completed(true) + .build(); + activityRepository.save(a); + }); + } + + public void deletePartnerActivities(@NonNull Partner partner) { + activityRepository.deleteByPartnerId(partner.getId()); + } + + public void addPartnerAddedActivity(@NonNull Partner partner) { + Optional existing = activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), + ActivityType.CONNECTION_REQUEST, + ActivityRole.CONNECTION_REQUEST_SENDER); + if (!existing.isPresent()) { + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_SENDER) + .state(ActivityState.CONNECTION_REQUEST_SENT) + .completed(true) + .build(); + activityRepository.save(a); + } + } + + public void addPartnerAcceptedActivity(@NonNull Partner partner) { + activityRepository.findByLinkIdAndTypeAndRole(partner.getId(), + ActivityType.CONNECTION_REQUEST, + ActivityRole.CONNECTION_REQUEST_SENDER).ifPresentOrElse(activity -> { + activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); + activity.setCompleted(true); + activityRepository.update(activity); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partner.getId()) + .partner(partner) + .type(ActivityType.CONNECTION_REQUEST) + .role(ActivityRole.CONNECTION_REQUEST_SENDER) + .state(ActivityState.CONNECTION_REQUEST_ACCEPTED) + .completed(true) + .build(); + activityRepository.save(a); + }); + } + + public void addPresentationExchangeTask(@NonNull PartnerProof partnerProof) { + partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { + // in case event is fired multiple times, see if already exists. + ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) + ? ActivityRole.PRESENTATION_EXCHANGE_PROVER + : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + ActivityState state = getPresentationExchangeState(partnerProof); + + Optional existing = activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), + ActivityType.PRESENTATION_EXCHANGE, + role); + + if (!existing.isPresent()) { + Activity a = Activity.builder() + .linkId(partnerProof.getId()) + .partner(partner) + .type(ActivityType.PRESENTATION_EXCHANGE) + .role(role) + .state(state) + .completed(ActivityState.PRESENTATION_EXCHANGE_SENT.equals(state)) + .build(); + activityRepository.save(a); + } + }); + } + + public void completePresentationExchangeTask(@NonNull PartnerProof partnerProof) { + partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { + // in case event is fired multiple times, see if already exists. + ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) + ? ActivityRole.PRESENTATION_EXCHANGE_PROVER + : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + ActivityState state = ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; + + activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), + ActivityType.PRESENTATION_EXCHANGE, + role).ifPresentOrElse(activity -> { + // set to completed and mark accepted + activity.setState(state); + activity.setCompleted(true); + activityRepository.update(activity); + }, () -> { + // add in a completed activity + Activity a = Activity.builder() + .linkId(partnerProof.getId()) + .partner(partner) + .type(ActivityType.PRESENTATION_EXCHANGE) + .role(role) + .state(state) + .completed(true) + .build(); + activityRepository.save(a); + }); + }); + } + + public void declinePresentationExchangeTask(@NonNull PartnerProof partnerProof) { + partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { + // in case event is fired multiple times, see if already exists. + ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) + ? ActivityRole.PRESENTATION_EXCHANGE_PROVER + : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + + activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), + ActivityType.PRESENTATION_EXCHANGE, + role).ifPresent(activity -> { + activity.setState(ActivityState.PRESENTATION_EXCHANGE_DECLINED); + activity.setCompleted(true); + activityRepository.update(activity); + }); + }); + } + + public void deletePresentationExchangeTask(@NonNull PartnerProof partnerProof) { + activityRepository.deleteByLinkIdAndType(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE); + } + + private ActivityItem convert(Activity activity) { + return ActivityItem.builder() + .linkId(activity.getLinkId().toString()) + .partner(converter.toAPIObject(activity.getPartner())) + .role(activity.getRole()) + .state(activity.getState()) + .type(activity.getType()) + .updatedAt(activity.getUpdatedAt().toEpochMilli()) + .completed(activity.isCompleted()) + .build(); + } + + private ActivityState getPresentationExchangeState(PartnerProof partnerProof) { + switch (partnerProof.getState()) { + case VERIFIED: + case PRESENTATION_ACKED: + return ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; + case REQUEST_SENT: + case PRESENTATIONS_SENT: + return ActivityState.PRESENTATION_EXCHANGE_SENT; + case REQUEST_RECEIVED: + case PRESENTATION_RECEIVED: + return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + default: + switch (partnerProof.getRole()) { + case VERIFIER: + return ActivityState.PRESENTATION_EXCHANGE_RECEIVED; + case PROVER: + default: + return ActivityState.PRESENTATION_EXCHANGE_SENT; + } + } + } + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java index d5fb80b72..dd4306dd8 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java @@ -19,14 +19,19 @@ import io.micronaut.runtime.event.annotation.EventListener; import io.micronaut.scheduling.annotation.Async; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; +import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.config.ActivityLogConfig; import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.notification.*; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.model.PartnerProof; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Optional; @Singleton @Slf4j @@ -41,28 +46,34 @@ public class NotificationEventListener { @Inject ActivityLogConfig activityLogConfig; + @Inject + Converter conv; + + @Inject + ActivityManager activityManager; + @EventListener @Async public void onCredentialAddedEvent(CredentialAddedEvent event) { log.debug("onCredentialAddedEvent"); // we have the connection id, but not the partner, will need to look up // partner... - partnerManager.getPartnerByConnectionId(event.getCredential().getConnectionId()).ifPresent(p -> { + PartnerAPI partnerAPI = partnerManager.getPartnerByConnectionId(event.getCredential().getConnectionId()); + if (partnerAPI != null) { WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onCredentialAdded, event.getCredential().getId().toString(), event.getCredential(), - p); + partnerAPI); messageService.sendMessage(message); + } + } - WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onNewTask, - event.getCredential().getId().toString(), - event.getCredential(), - p); - messageService.sendMessage(task); - - }); + @EventListener + @Async + public void onPartnerRequestCompletedEvent(PartnerRequestCompletedEvent event) { + log.debug("onPartnerRequestCompletedEvent"); + activityManager.completePartnerRequestTask(event.getPartner()); } @EventListener @@ -71,11 +82,14 @@ public void onPartnerRequestReceivedEvent(PartnerRequestReceivedEvent event) { log.debug("onPartnerRequestReceivedEvent"); // only notify if this is a task (requires manual intervention) if (activityLogConfig.getConnectionStatesForTasks().contains(event.getPartner().getState())) { + + activityManager.addPartnerRequestReceivedTask(event.getPartner()); + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onPartnerRequestReceived, - event.getPartner().getId(), + event.getPartner().getId().toString(), null, - event.getPartner()); + conv.toAPIObject(event.getPartner())); messageService.sendMessage(message); } } @@ -86,10 +100,26 @@ public void onPartnerAddedEvent(PartnerAddedEvent event) { log.debug("onPartnerAddedEvent"); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onPartnerAdded, - event.getPartner().getId(), + event.getPartner().getId().toString(), null, - event.getPartner()); + conv.toAPIObject(event.getPartner())); messageService.sendMessage(message); + + activityManager.addPartnerAddedActivity(event.getPartner()); + } + + @EventListener + @Async + public void onPartnerAcceptedEvent(PartnerAcceptedEvent event) { + log.debug("onPartnerAcceptedEvent"); + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.onPartnerAccepted, + event.getPartner().getId().toString(), + null, + conv.toAPIObject(event.getPartner())); + messageService.sendMessage(message); + + activityManager.addPartnerAcceptedActivity(event.getPartner()); } @EventListener @@ -98,10 +128,12 @@ public void onPartnerRemovedEvent(PartnerRemovedEvent event) { log.debug("onPartnerRemovedEvent"); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onPartnerRemoved, - event.getPartner().getId(), + event.getPartner().getId().toString(), null, - event.getPartner()); + conv.toAPIObject(event.getPartner())); messageService.sendMessage(message); + + activityManager.deletePartnerActivities(event.getPartner()); } @EventListener @@ -109,47 +141,82 @@ public void onPartnerRemovedEvent(PartnerRemovedEvent event) { public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEvent event) { log.debug("onPresentationRequestCompletedEvent"); // we have the partner id, but not the partner, will need to look up partner... - partnerManager.getPartnerById(event.getProofExchange().getPartnerId()).ifPresent(p -> { + partnerManager.getPartnerById(event.getPartnerProof().getPartnerId()).ifPresent(p -> { WebSocketMessageBody message = null; - if (event.getProofExchange().getRole().equals(PresentationExchangeRole.PROVER)) { + if (event.getPartnerProof().getRole().equals(PresentationExchangeRole.PROVER)) { message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onPresentationProved, - event.getProofExchange().getId().toString(), - event.getProofExchange(), + event.getPartnerProof().getId().toString(), + conv.toAPIObject(event.getPartnerProof()), p); } else { message = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onPresentationVerified, - event.getProofExchange().getId().toString(), - event.getProofExchange(), + event.getPartnerProof().getId().toString(), + conv.toAPIObject(event.getPartnerProof()), p); } + activityManager.completePresentationExchangeTask(event.getPartnerProof()); messageService.sendMessage(message); }); } + @EventListener + @Async + public void onPresentationRequestDeclinedEvent(PresentationRequestDeclinedEvent event) { + log.debug("onPresentationRequestDeclinedEvent"); + activityManager.declinePresentationExchangeTask(event.getPartnerProof()); + } + + @EventListener + @Async + public void onPresentationRequestDeletedEvent(PresentationRequestDeletedEvent event) { + log.debug("onPresentationRequestDeletedEvent"); + activityManager.deletePresentationExchangeTask(event.getPartnerProof()); + } + @EventListener @Async public void onPresentationRequestReceivedEvent(PresentationRequestReceivedEvent event) { log.debug("onPresentationRequestReceivedEvent"); - // only notify if this is a task (requires manual intervention) - if (activityLogConfig.getPresentationExchangeStatesForTasks().contains(event.getProofExchange().getState())) { - // we have the partner id, but not the partner, will need to look up partner... - partnerManager.getPartnerById(event.getProofExchange().getPartnerId()).ifPresent(p -> { + handlePresentationRequestEvent(event.getPartnerProof(), + WebSocketMessageBody.WebSocketMessageType.onPresentationRequestReceived); + } + + @EventListener + @Async + public void onPresentationRequestSentEvent(PresentationRequestSentEvent event) { + log.debug("onPresentationRequestSentEvent"); + handlePresentationRequestEvent(event.getPartnerProof(), + WebSocketMessageBody.WebSocketMessageType.onPresentationRequestSent); + } + + private void handlePresentationRequestEvent(@NonNull PartnerProof partnerProof, + WebSocketMessageBody.WebSocketMessageType messageType) { + Optional partnerAPI = partnerManager.getPartnerById(partnerProof.getPartnerId()); + if (partnerAPI.isPresent()) { + PartnerAPI p = partnerAPI.get(); + + activityManager.addPresentationExchangeTask(partnerProof); + + // only notify if this is a task (requires manual intervention) + if (activityLogConfig.getPresentationExchangeStatesForTasks().contains(partnerProof.getState())) { + // we have the partner id, but not the partner, will need to look up partner... WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPresentationRequestReceived, - event.getProofExchange().getId().toString(), - event.getProofExchange(), + messageType, + partnerProof.getId().toString(), + conv.toAPIObject(partnerProof), p); messageService.sendMessage(message); WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( WebSocketMessageBody.WebSocketMessageType.onNewTask, - event.getProofExchange().getId().toString(), - event.getProofExchange(), + partnerProof.getId().toString(), + conv.toAPIObject(partnerProof), p); messageService.sendMessage(task); - }); + } } } + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java index 3497f3e2a..3818eb2fb 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/PartnerManager.java @@ -87,10 +87,6 @@ public Optional getPartnerById(@NonNull UUID id) { return repo.findById(id).map(converter::toAPIObject); } - public Optional getPartnerByConnectionId(@NonNull String connectionId) { - return repo.findByConnectionId(connectionId).map(converter::toAPIObject); - } - @Nullable public PartnerAPI getPartner(@NonNull UUID id) { return repo.findById(id).map(converter::toAPIObject).orElse(null); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java index 74d582cf3..43466b67a 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java @@ -36,16 +36,12 @@ import org.hyperledger.aries.api.out_of_band.CreateInvitationFilter; import org.hyperledger.aries.api.present_proof.PresentProofRecordsFilter; import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; -import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.config.BPAMessageSource; -import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.controller.api.partner.CreatePartnerInvitationRequest; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.activity.DidResolver; -import org.hyperledger.bpa.impl.notification.PartnerRequestReceivedEvent; -import org.hyperledger.bpa.impl.notification.PartnerAddedEvent; -import org.hyperledger.bpa.impl.notification.PartnerRemovedEvent; +import org.hyperledger.bpa.impl.notification.*; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.Partner; import org.hyperledger.bpa.model.PartnerProof; @@ -180,10 +176,11 @@ public void handleOutgoingConnectionEvent(ConnectionRecord record) { } else { partnerRepo.updateState(dbP.getId(), record.getState()); } - // should it be on request, when you ask for new partner, or when partner - // accepts? if (ConnectionState.REQUEST.equals(record.getState())) { - eventPublisher.publishEvent(PartnerAddedEvent.builder().partner(conv.toAPIObject(dbP)).build()); + eventPublisher.publishEventAsync(PartnerAddedEvent.builder().partner(dbP).build()); + } else if (ConnectionState.RESPONSE.equals(record.getState()) || + ConnectionState.COMPLETED.equals(record.getState())) { + eventPublisher.publishEventAsync(PartnerAcceptedEvent.builder().partner(dbP).build()); } }); } @@ -239,11 +236,11 @@ private void resolveAndSend(ConnectionRecord record, Partner p) { // only incoming connections in state request if (ConnectionState.REQUEST.equals(record.getState())) { didResolver.lookupIncoming(p); - PartnerAPI o = conv.toAPIObject(p); - messageService.sendMessage(WebSocketMessageBody.partnerReceived(o)); if (record.isIncomingConnection()) { - eventPublisher.publishEvent(PartnerRequestReceivedEvent.builder().partner(o).build()); + eventPublisher.publishEventAsync(PartnerRequestReceivedEvent.builder().partner(p).build()); } + } else if (ConnectionState.COMPLETED.equals(record.getState()) && record.isIncomingConnection()) { + eventPublisher.publishEventAsync(PartnerRequestCompletedEvent.builder().partner(p).build()); } } @@ -257,9 +254,7 @@ public void removeConnection(String connectionId) { } Optional partner = partnerRepo.findByConnectionId(connectionId); - final PartnerAPI[] partnerAPI = { null }; partner.ifPresent(p -> { - partnerAPI[0] = conv.toAPIObject(p); final List proofs = partnerProofRepo.findByPartnerId(p.getId()); if (CollectionUtils.isNotEmpty(proofs)) { partnerProofRepo.deleteAll(proofs); @@ -283,8 +278,8 @@ public void removeConnection(String connectionId) { }); myCredRepo.updateByConnectionId(connectionId, null); - if (partnerAPI[0] != null) { - eventPublisher.publishEvent(PartnerRemovedEvent.builder().partner(partnerAPI[0]).build()); + if (partner.isPresent()) { + eventPublisher.publishEventAsync(PartnerRemovedEvent.builder().partner(partner.get()).build()); } } catch (IOException e) { log.error("Could not delete connection: {}", connectionId, e); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java index d85309620..9aad39f50 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java @@ -176,7 +176,7 @@ public void handleCredentialAcked(V1CredentialExchange credEx) { MyCredential updated = credRepo.update(cred); AriesCredential ariesCredential = buildAriesCredential(updated); messageService.sendMessage(WebSocketMessageBody.credentialReceived(ariesCredential)); - eventPublisher.publishEvent(CredentialAddedEvent.builder() + eventPublisher.publishEventAsync(CredentialAddedEvent.builder() .credential(ariesCredential) .credentialExchange(credEx) .build()); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java index dbdf4c996..6e9eb7f2f 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java @@ -28,6 +28,7 @@ import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.notification.PresentationRequestCompletedEvent; +import org.hyperledger.bpa.impl.notification.PresentationRequestDeclinedEvent; import org.hyperledger.bpa.impl.notification.PresentationRequestReceivedEvent; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.PartnerProof; @@ -88,6 +89,8 @@ private void handleAll(PresentationExchangeRecord proof) { } if (proof.getErrorMsg() != null) { pProofRepo.updateProblemReport(pp.getId(), proof.getErrorMsg()); + eventPublisher.publishEventAsync( + PresentationRequestDeclinedEvent.builder().partnerProof(pp).build()); } }, () -> pProofRepo.save(defaultProof(p.getId(), proof))), @@ -112,8 +115,8 @@ private void handleAckedOrVerified(PresentationExchangeRecord proof) { state, WebSocketMessageBody.WebSocketMessageType.PROOF, savedProof); - eventPublisher.publishEvent(PresentationRequestCompletedEvent.builder() - .proofExchange(conv.toAPIObject(savedProof)) + eventPublisher.publishEventAsync(PresentationRequestCompletedEvent.builder() + .partnerProof(savedProof) .build()); } else { log.warn("Proof does not contain any identifiers event will not be persisted"); @@ -148,8 +151,8 @@ private void handleProofRequest(@NonNull PresentationExchangeRecord proof) { final PartnerProof pp = defaultProof(p.getId(), proof) .setProofRequest(proof.getPresentationRequest()); pProofRepo.save(pp); - eventPublisher.publishEvent(PresentationRequestReceivedEvent.builder() - .proofExchange(conv.toAPIObject(pp)) + eventPublisher.publishEventAsync(PresentationRequestReceivedEvent.builder() + .partnerProof(pp) .build()); })); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java index 89a5e1a48..6d2724882 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java @@ -18,6 +18,7 @@ package org.hyperledger.bpa.impl.aries; import io.micronaut.context.annotation.Value; +import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; @@ -39,6 +40,8 @@ import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.activity.DidResolver; import org.hyperledger.bpa.impl.aries.config.SchemaService; +import org.hyperledger.bpa.impl.notification.PresentationRequestDeletedEvent; +import org.hyperledger.bpa.impl.notification.PresentationRequestSentEvent; import org.hyperledger.bpa.impl.util.AriesStringUtil; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.impl.util.TimeUtil; @@ -90,6 +93,9 @@ public class ProofManager { @Inject MessageService messageService; + @Inject + ApplicationEventPublisher eventPublisher; + // request proof from partner public void sendPresentProofRequest(@NonNull UUID partnerId, @NonNull RequestProofRequest req) { try { @@ -115,6 +121,10 @@ public void sendPresentProofRequest(@NonNull UUID partnerId, @NonNull RequestPro .issuer(req.getFirstIssuerDid()) .build(); pProofRepo.save(pp); + + eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() + .partnerProof(pp) + .build()); }); } else { throw new PartnerException("Could not find any schema on the ledger for id: " @@ -130,6 +140,10 @@ public void sendPresentProofRequest(@NonNull UUID partnerId, @NonNull RequestPro .role(exchange.getRole()) .build(); pProofRepo.save(pp); + + eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() + .partnerProof(pp) + .build()); }); } } else { @@ -148,6 +162,8 @@ public void declinePresentProofRequest(@NotNull PartnerProof proofEx, String exp try { sendPresentProofProblemReport(proofEx.getPresentationExchangeId(), explainString); deletePartnerProof(proofEx.getId()); + eventPublisher + .publishEventAsync(PresentationRequestDeletedEvent.builder().partnerProof(proofEx).build()); } catch (IOException e) { throw new NetworkException(ACA_PY_ERROR_MSG, e); } catch (AriesException e) { @@ -240,6 +256,10 @@ public void sendProofProposal(@NonNull UUID partnerId, @NonNull UUID myCredentia .issuer(resolveIssuer(cred.getCredentialDefinitionId())) .build(); pProofRepo.save(pp); + + eventPublisher.publishEventAsync(PresentationRequestSentEvent.builder() + .partnerProof(pp) + .build()); }); } catch (IOException e) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAcceptedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAcceptedEvent.java new file mode 100644 index 000000000..b4fa7c650 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAcceptedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.Partner; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PartnerAcceptedEvent { + + private Partner partner; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java index 464a77bff..a1be0e1ad 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerAddedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.api.PartnerAPI; +import org.hyperledger.bpa.model.Partner; @Builder @NoArgsConstructor @@ -29,5 +29,5 @@ @Getter public class PartnerAddedEvent { - private PartnerAPI partner; + private Partner partner; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java index 7d2e71444..e48e12a13 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRemovedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.api.PartnerAPI; +import org.hyperledger.bpa.model.Partner; @Builder @NoArgsConstructor @@ -29,5 +29,5 @@ @Getter public class PartnerRemovedEvent { - private PartnerAPI partner; + private Partner partner; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestCompletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestCompletedEvent.java new file mode 100644 index 000000000..c8116d02e --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestCompletedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.Partner; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PartnerRequestCompletedEvent { + + private Partner partner; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java index b626fb9b3..e695e51fc 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PartnerRequestReceivedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.api.PartnerAPI; +import org.hyperledger.bpa.model.Partner; @Builder @NoArgsConstructor @@ -29,6 +29,6 @@ @Getter public class PartnerRequestReceivedEvent { - private PartnerAPI partner; + private Partner partner; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java index 7bfab202c..b5614068e 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestCompletedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.api.aries.AriesProofExchange; +import org.hyperledger.bpa.model.PartnerProof; @Builder @NoArgsConstructor @@ -29,6 +29,6 @@ @Getter public class PresentationRequestCompletedEvent { - private AriesProofExchange proofExchange; + private PartnerProof partnerProof; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeclinedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeclinedEvent.java new file mode 100644 index 000000000..022db9a76 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeclinedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.PartnerProof; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PresentationRequestDeclinedEvent { + + private PartnerProof partnerProof; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeletedEvent.java new file mode 100644 index 000000000..5dcc7361f --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestDeletedEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.PartnerProof; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PresentationRequestDeletedEvent { + + private PartnerProof partnerProof; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java index 8c41cbf31..1d919d5f8 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestReceivedEvent.java @@ -21,7 +21,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hyperledger.bpa.api.aries.AriesProofExchange; +import org.hyperledger.bpa.model.PartnerProof; @Builder @NoArgsConstructor @@ -29,6 +29,6 @@ @Getter public class PresentationRequestReceivedEvent { - private AriesProofExchange proofExchange; + private PartnerProof partnerProof; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestSentEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestSentEvent.java new file mode 100644 index 000000000..0309ffcbd --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/PresentationRequestSentEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.PartnerProof; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class PresentationRequestSentEvent { + + private PartnerProof partnerProof; + +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java index d42e6a5d5..08d0d0bf8 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/Activity.java @@ -17,9 +17,16 @@ */ package org.hyperledger.bpa.model; +import io.micronaut.data.annotation.AutoPopulated; +import io.micronaut.data.annotation.DateCreated; +import io.micronaut.data.annotation.DateUpdated; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hyperledger.bpa.controller.api.activity.ActivityRole; +import org.hyperledger.bpa.controller.api.activity.ActivityState; +import org.hyperledger.bpa.controller.api.activity.ActivityType; import javax.persistence.*; import java.time.Instant; @@ -28,19 +35,33 @@ @Data @NoArgsConstructor @AllArgsConstructor +@Builder @Entity -@Table(name = "activity_vw") +@Table(name = "activity") public class Activity { @Id + @AutoPopulated private UUID id; + private UUID linkId; + @OneToOne private Partner partner; - private String type; - private String role; - private String state; + @Enumerated(EnumType.STRING) + private ActivityType type; + + @Enumerated(EnumType.STRING) + private ActivityRole role; + + @Enumerated(EnumType.STRING) + private ActivityState state; + + private boolean completed; + @DateCreated + private Instant createdAt; + @DateUpdated private Instant updatedAt; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java index 44038680e..2be37e0d7 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/repository/ActivityRepository.java @@ -21,22 +21,53 @@ import io.micronaut.data.annotation.Join; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; -import io.micronaut.data.repository.GenericRepository; +import io.micronaut.data.repository.CrudRepository; +import org.hyperledger.bpa.controller.api.activity.ActivityRole; +import org.hyperledger.bpa.controller.api.activity.ActivityType; import org.hyperledger.bpa.model.Activity; import java.util.List; +import java.util.Optional; import java.util.UUID; @JdbcRepository(dialect = Dialect.POSTGRES) -public interface ActivityRepository extends GenericRepository { +public interface ActivityRepository extends CrudRepository { - // @Join and @Query do not work together - // Need @Query to select in 2 lists, but since not possible? let's take - // advantage of the join to not have N+1 queries to get the partner - // Will have to process the result set to match the states we want... - // @Query("SELECT act.* FROM activity_vw as act where act.type in (:types) and - // act.state in (:states)") @Join(value = "partner", type = Join.Type.LEFT_FETCH) - Iterable findByTypeIn(@NonNull List types); + List findByPartnerId(@NonNull UUID partnerId); + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByLinkId(@NonNull UUID linkId); + + Optional findByLinkIdAndTypeAndRole(@NonNull UUID linkId, + @NonNull ActivityType type, + @NonNull ActivityRole role); + + void updateByLinkIdAndTypeAndRole(@NonNull UUID linkId, + @NonNull ActivityType type, + @NonNull ActivityRole role, + boolean completed); + + void deleteByLinkIdAndType(@NonNull UUID linkId, + @NonNull ActivityType type); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List listOrderByUpdatedAtDesc(); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByTypeOrderByUpdatedAt(@NonNull ActivityType type); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByCompletedFalseOrderByUpdatedAtDesc(); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByTypeAndCompletedFalseOrderByUpdatedAtDesc(@NonNull ActivityType type); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByCompletedTrueOrderByUpdatedAtDesc(); + + @Join(value = "partner", type = Join.Type.LEFT_FETCH) + List findByTypeAndCompletedTrueOrderByUpdatedAtDesc(@NonNull ActivityType type); + + void deleteByPartnerId(@NonNull UUID partnerId); } diff --git a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-table.sql b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-table.sql new file mode 100644 index 000000000..9ffab6097 --- /dev/null +++ b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-table.sql @@ -0,0 +1,11 @@ +CREATE TABLE activity ( + id uuid PRIMARY KEY, + partner_id uuid NOT NULL, + link_id uuid NOT NULL, + type character varying(255), + role character varying(255), + state character varying(255), + completed boolean NOT NULL, + created_at timestamp without time zone, + updated_at timestamp without time zone +); diff --git a/backend/business-partner-agent/src/main/resources/log4j2.xml b/backend/business-partner-agent/src/main/resources/log4j2.xml index 4372838c3..fdfe3ca7f 100644 --- a/backend/business-partner-agent/src/main/resources/log4j2.xml +++ b/backend/business-partner-agent/src/main/resources/log4j2.xml @@ -11,7 +11,7 @@ - + diff --git a/frontend/src/components/ActivityList.vue b/frontend/src/components/ActivityList.vue index 834017048..79c6b74f0 100644 --- a/frontend/src/components/ActivityList.vue +++ b/frontend/src/components/ActivityList.vue @@ -172,15 +172,48 @@ id: item.linkId, }, }); - } - // TODO: change this to go to the Proof/Presentation details screen when it exists... - if (item.type === ActivityTypes.PRESENTATION_EXCHANGE.value) { - this.$router.push({ + } else if (item.type === ActivityTypes.PRESENTATION_EXCHANGE.value) { + // by default, just go to the partner screen... + let route = { name: "Partner", params: { - id: item.linkId, + id: item.partner.id, }, - }); + }; + if (item.role === ActivityRoles.PRESENTATION_EXCHANGE_PROVER.value) { + // if role prover and received -> presentation-request/6a693ed0-8978-4ff7-a15a-343be7b4cdb4/details + // if prover and accepted -> partners/a0ebe43f-8fdd-4ab0-a3d5-07d4d8ca42f9/presentation/6a693ed0-8978-4ff7-a15a-343be7b4cdb4 + if (item.state === ActivityStates.PRESENTATION_EXCHANGE_RECEIVED.value) { + route = { + name: "PresentationRequestDetails", + params: { + id: item.linkId, + }, + }; + } else if (item.state === ActivityStates.PRESENTATION_EXCHANGE_ACCEPTED.value) { + route = { + name: "Presentation", + params: { + id: item.partner.id, + presentationId: item.linkId, + }, + }; + } + } else if (item.role === ActivityRoles.PRESENTATION_EXCHANGE_VERIFIER.value) { + // if verifier and sent -> nothing go to partner + // if verifier and declined -> nothing go to partner + // if verifier and accepted -> partners/a0ebe43f-8fdd-4ab0-a3d5-07d4d8ca42f9/presentation/80311f0c-2738-4cb4-9d0c-3cc4d81742aa + if (item.state === ActivityStates.PRESENTATION_EXCHANGE_ACCEPTED.value) { + route = { + name: "Presentation", + params: { + id: item.partner.id, + presentationId: item.linkId, + }, + }; + } + } + this.$router.push(route); } }, diff --git a/frontend/src/constants.js b/frontend/src/constants.js index b091d9590..be1081883 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -105,10 +105,6 @@ export const ActivityTypes = Object.freeze({ value: "connection_request", label: i18n.t("constants.activityTypes.connectionRequest"), }, - CREDENTIAL_OFFER: { - value: "credential_offer", - label: i18n.t("constants.activityTypes.credentialOffer"), - }, PRESENTATION_EXCHANGE: { value: "presentation_exchange", label: i18n.t("constants.activityTypes.presentationExchange"), @@ -132,6 +128,10 @@ export const ActivityStates = Object.freeze({ value: "presentation_exchange_accepted", label: i18n.t("constants.activityStates.presentationExchange.accepted"), }, + PRESENTATION_EXCHANGE_DECLINED: { + value: "presentation_exchange_declined", + label: i18n.t("constants.activityStates.presentationExchange.declined"), + }, PRESENTATION_EXCHANGE_RECEIVED: { value: "presentation_exchange_received", label: i18n.t("constants.activityStates.presentationExchange.received"), diff --git a/frontend/src/locales/bcgov.json b/frontend/src/locales/bcgov.json index daff96edd..74cca33c8 100644 --- a/frontend/src/locales/bcgov.json +++ b/frontend/src/locales/bcgov.json @@ -27,6 +27,7 @@ "activityStates": { "presentationExchange": { "accepted": "Proof accepted", + "declined": "Request for proof has been declined", "received": "A Proof has been requested", "sent": "Request for Proof sent" } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index ca6a7c600..01242036e 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -118,6 +118,7 @@ }, "presentationExchange": { "accepted": "Proof Presentation accepted", + "declined": "A Presentation of Proof has been declined", "received": "A Presentation of Proof has been requested", "sent": "Request for Presentation of Proof sent" } From b79ced326e7bed07d6ce1a9e4ef3224b68d12e54 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Wed, 28 Jul 2021 16:04:03 -0700 Subject: [PATCH 4/8] remove the view Signed-off-by: Jason Sherman --- .../databasemigrations/V1.17__add-activity-view.sql | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql diff --git a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql deleted file mode 100644 index e507ec95d..000000000 --- a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.17__add-activity-view.sql +++ /dev/null @@ -1,10 +0,0 @@ -create or replace view activity_vw as -select t.* from -(select p.id as partner_id, 'CONNECTION_REQUEST' as type, p.id, p.state, case when p.incoming then 'CONNECTION_REQUEST_RECIPIENT' else 'CONNECTION_REQUEST_SENDER' end as role, p.updated_at from partner as p -union -select pp.partner_id, 'PRESENTATION_EXCHANGE' as type, pp.id, pp.state, pp.role, case when pp.issued_at is not null then pp.issued_at else pp.created_at end as updated_at from partner_proof as pp -union -select bce.partner_id, 'CREDENTIAL_EXCHANGE' as type, bce.id, bce.state, bce.role, bce.updated_at from bpa_credential_exchange as bce -union -select mcp.id as partner_id, 'CREDENTIAL_EXCHANGE' as type, mc.id, mc.state, 'HOLDER' as role, mc.issued_at as updated_at from my_credential as mc inner join partner as mcp on mc.connection_id = mcp.connection_id) as t -order by t.updated_at desc; From b23df4781c165897ca043776814a9c84774bbc62 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Wed, 28 Jul 2021 16:35:56 -0700 Subject: [PATCH 5/8] move basic messaging handling into current stack (further refactor later when we do persistence). Signed-off-by: Jason Sherman --- .../bpa/impl/ActivitiesEventHandler.java | 66 ------------------- .../bpa/impl/NotificationEventListener.java | 12 ++++ .../bpa/impl/aries/AriesEventHandler.java | 6 ++ .../bpa/impl/aries/ConnectionManager.java | 5 ++ .../BasicMessageReceivedEvent.java | 32 +++++++++ 5 files changed, 55 insertions(+), 66 deletions(-) delete mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/BasicMessageReceivedEvent.java diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java deleted file mode 100644 index 35fc69db5..000000000 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivitiesEventHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2020-2021 - for information on the respective copyright owner - * see the NOTICE file and/or the repository at - * https://github.com/hyperledger-labs/business-partner-agent - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.hyperledger.bpa.impl; - -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.hyperledger.aries.webhook.EventHandler; -import org.hyperledger.bpa.config.ActivityLogConfig; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Slf4j -@Singleton -@NoArgsConstructor -public class ActivitiesEventHandler extends EventHandler { - @Inject - MessageService messageService; - - @Inject - ActivityLogConfig activityLogConfig; - - @Inject - PartnerManager partnerManager; - /* - * @Override public void handleConnection(ConnectionRecord connection) { Boolean - * completed = null; boolean notify = true; if - * (activityLogConfig.getConnectionStatesForTasks().contains(connection.getState - * ())) { completed = false; // we do not always want to notify... it is only a - * task on incoming request notify = connection.isIncomingConnection(); } else - * if (activityLogConfig.getConnectionStatesCompleted().contains(connection. - * getState())) { completed = true; } if (completed != null && notify) { - * messageService .sendMessage(WebSocketMessageBody.notification(ActivityType. - * CONNECTION_REQUEST, completed)); } } - * - * @Override public void handleProof(PresentationExchangeRecord proof) { Boolean - * completed = null; if - * (activityLogConfig.getPresentationExchangeStatesForTasks().contains(proof. - * getState())) { completed = false; } else if - * (activityLogConfig.getPresentationExchangeStatesCompleted().contains(proof. - * getState())) { completed = true; } if (completed != null) { messageService - * .sendMessage(WebSocketMessageBody.notification(ActivityType. - * PRESENTATION_EXCHANGE, completed)); } } - * - * @Override public void handleBasicMessage(BasicMessage message) { PartnerAPI - * partner = partnerManager.getPartnerByConnectionId(message.getConnectionId()); - * if (partner != null) { - * messageService.sendMessage(WebSocketMessageBody.message(partner, message)); } - * } - */ -} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java index dd4306dd8..3b824e182 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java @@ -191,6 +191,18 @@ public void onPresentationRequestSentEvent(PresentationRequestSentEvent event) { WebSocketMessageBody.WebSocketMessageType.onPresentationRequestSent); } + @EventListener + @Async + public void onBasicMessageReceivedEvent(BasicMessageReceivedEvent event) { + log.debug("onBasicMessageReceivedEvent"); + // refactor once we have persistence of messages... + PartnerAPI partner = partnerManager.getPartnerByConnectionId(event.getMessage().getConnectionId()); + if (partner != null) { + WebSocketMessageBody message = WebSocketMessageBody.message(partner, event.getMessage()); + messageService.sendMessage(message); + } + } + private void handlePresentationRequestEvent(@NonNull PartnerProof partnerProof, WebSocketMessageBody.WebSocketMessageType messageType) { Optional partnerAPI = partnerManager.getPartnerById(partnerProof.getPartnerId()); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java index 1550d007a..72618e94a 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java @@ -22,6 +22,7 @@ import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.aries.api.message.BasicMessage; import org.hyperledger.aries.api.message.PingEvent; import org.hyperledger.aries.api.message.ProblemReport; import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; @@ -115,6 +116,11 @@ public void handleProblemReport(ProblemReport report) { proofMgmt.handleProblemReport(report.getThread().getThid(), report.getDescription()); } + @Override + public void handleBasicMessage(BasicMessage message) { + conMgmt.receiveMessage(message); + } + @Override public void handleRaw(String eventType, String json) { log.trace(json); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java index 43466b67a..e3db6d5d1 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ConnectionManager.java @@ -33,6 +33,7 @@ import org.hyperledger.aries.api.connection.*; import org.hyperledger.aries.api.did_exchange.DidExchangeCreateRequestFilter; import org.hyperledger.aries.api.exception.AriesException; +import org.hyperledger.aries.api.message.BasicMessage; import org.hyperledger.aries.api.out_of_band.CreateInvitationFilter; import org.hyperledger.aries.api.present_proof.PresentProofRecordsFilter; import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; @@ -297,6 +298,10 @@ public void sendMessage(String connectionId, String content) { } } + public void receiveMessage(BasicMessage message) { + eventPublisher.publishEventAsync(BasicMessageReceivedEvent.builder().message(message).build()); + } + private InvitationRecord createOOBInvitation(@Nullable String alias) throws IOException { return ac.outOfBandCreateInvitation( InvitationCreateRequest.builder() diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/BasicMessageReceivedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/BasicMessageReceivedEvent.java new file mode 100644 index 000000000..2f1eb710c --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/BasicMessageReceivedEvent.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.aries.api.message.BasicMessage; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class BasicMessageReceivedEvent { + private BasicMessage message; +} From ad2ab44e239988fc70a11986689c2c3ab36b3053 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Wed, 28 Jul 2021 16:42:45 -0700 Subject: [PATCH 6/8] isolate use of messageService to the notification event listener. Signed-off-by: Jason Sherman --- .../hyperledger/bpa/impl/aries/CredentialManager.java | 4 +--- .../hyperledger/bpa/impl/aries/ProofEventHandler.java | 9 --------- .../org/hyperledger/bpa/impl/aries/ProofManager.java | 8 -------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java index 9aad39f50..146c28325 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java @@ -40,7 +40,6 @@ import org.hyperledger.bpa.api.aries.ProfileVC; import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.api.exception.PartnerException; -import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.activity.LabelStrategy; import org.hyperledger.bpa.impl.activity.VPManager; @@ -175,7 +174,6 @@ public void handleCredentialAcked(V1CredentialExchange credEx) { .setLabel(label); MyCredential updated = credRepo.update(cred); AriesCredential ariesCredential = buildAriesCredential(updated); - messageService.sendMessage(WebSocketMessageBody.credentialReceived(ariesCredential)); eventPublisher.publishEventAsync(CredentialAddedEvent.builder() .credential(ariesCredential) .credentialExchange(credEx) @@ -265,7 +263,7 @@ public void deleteCredentialById(@NonNull UUID id) { * * @param ariesCred {@link Credential} * @return the issuer or null when the credential or the credential definition - * id is null + * id is null */ @Nullable String resolveIssuer(@Nullable Credential ariesCred) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java index 6e9eb7f2f..d6eaade30 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofEventHandler.java @@ -25,7 +25,6 @@ import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; import org.hyperledger.aries.api.present_proof.PresentationExchangeState; -import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.notification.PresentationRequestCompletedEvent; import org.hyperledger.bpa.impl.notification.PresentationRequestDeclinedEvent; @@ -107,14 +106,6 @@ private void handleAckedOrVerified(PresentationExchangeRecord proof) { pProofRepo.findByPresentationExchangeId(proof.getPresentationExchangeId()).ifPresent(pp -> { if (CollectionUtils.isNotEmpty(proof.getIdentifiers())) { PartnerProof savedProof = proofManager.handleAckedOrVerifiedProofEvent(proof, pp); - - WebSocketMessageBody.WebSocketMessageState state = PresentationExchangeRole.VERIFIER - .equals(proof.getRole()) ? WebSocketMessageBody.WebSocketMessageState.RECEIVED - : WebSocketMessageBody.WebSocketMessageState.SENT; - proofManager.sendMessage( - state, - WebSocketMessageBody.WebSocketMessageType.PROOF, - savedProof); eventPublisher.publishEventAsync(PresentationRequestCompletedEvent.builder() .partnerProof(savedProof) .build()); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java index 6d2724882..126d16881 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/ProofManager.java @@ -35,7 +35,6 @@ import org.hyperledger.bpa.api.exception.PartnerException; import org.hyperledger.bpa.api.exception.PresentationConstructionException; import org.hyperledger.bpa.api.exception.WrongApiUsageException; -import org.hyperledger.bpa.controller.api.WebSocketMessageBody; import org.hyperledger.bpa.controller.api.partner.RequestProofRequest; import org.hyperledger.bpa.impl.MessageService; import org.hyperledger.bpa.impl.activity.DidResolver; @@ -300,13 +299,6 @@ public void deletePartnerProof(@NonNull UUID id) { }); } - void sendMessage( - @NonNull WebSocketMessageBody.WebSocketMessageState state, - @NonNull WebSocketMessageBody.WebSocketMessageType type, - @NonNull PartnerProof pp) { - messageService.sendMessage(WebSocketMessageBody.proof(state, type, conv.toAPIObject(pp))); - } - private @Nullable String resolveIssuer(String credDefId) { String issuer = null; if (StringUtils.isNotEmpty(credDefId)) { From 0d5ebe6768c400075f68e04cdb67f9db973cd640 Mon Sep 17 00:00:00 2001 From: Jason Sherman Date: Thu, 29 Jul 2021 12:16:30 -0700 Subject: [PATCH 7/8] hook backend events to frontend notifications. add indicators on sub-objects (presentations). Signed-off-by: Jason Sherman --- .../controller/api/WebSocketMessageBody.java | 98 ++------ .../controller/api/activity/ActivityItem.java | 1 + .../hyperledger/bpa/impl/ActivityManager.java | 45 +++- .../bpa/impl/NotificationEventListener.java | 60 +++-- .../bpa/impl/aries/CredentialManager.java | 2 +- .../bpa/impl/notification/TaskAddedEvent.java | 32 +++ .../impl/notification/TaskCompletedEvent.java | 32 +++ frontend/src/App.vue | 41 ++-- frontend/src/components/ActivityList.vue | 18 +- frontend/src/components/CredExList.vue | 14 ++ frontend/src/components/MyCredentialList.vue | 26 +-- frontend/src/components/NewMessageIcon.vue | 79 +++++-- frontend/src/components/PartnerList.vue | 29 +-- frontend/src/components/PresentationList.vue | 9 +- .../components/PresentationRequestList.vue | 8 + .../src/components/messages/BasicMessages.vue | 6 +- .../components/tableHeaders/PartnerHeaders.js | 14 +- .../tableHeaders/PresentationListHeaders.js | 8 +- frontend/src/main.js | 22 +- frontend/src/plugins/vuetify.js | 1 + frontend/src/store/index.js | 2 + frontend/src/store/modules/messages.js | 37 ++++ frontend/src/store/modules/notifications.js | 209 +++++++----------- frontend/src/store/modules/socketevents.js | 112 ---------- frontend/src/views/Credential.vue | 2 +- frontend/src/views/Dashboard.vue | 16 +- frontend/src/views/Notifications.vue | 3 +- frontend/src/views/Partner.vue | 11 +- frontend/src/views/Presentation.vue | 1 + .../src/views/PresentationRequestDetails.vue | 1 + 30 files changed, 477 insertions(+), 462 deletions(-) create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskAddedEvent.java create mode 100644 backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskCompletedEvent.java create mode 100644 frontend/src/store/modules/messages.js diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java index fbbd00d75..38ac1a4b5 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/WebSocketMessageBody.java @@ -21,8 +21,6 @@ import lombok.*; import org.hyperledger.aries.api.message.BasicMessage; import org.hyperledger.bpa.api.PartnerAPI; -import org.hyperledger.bpa.api.aries.AriesCredential; -import org.hyperledger.bpa.api.aries.AriesProofExchange; /** * Websocket events @@ -45,93 +43,38 @@ public class WebSocketMessageBody { @Builder public static final class WebSocketMessage { private WebSocketMessageType type; - private WebSocketMessageState state; private String linkId; private Object info; private PartnerAPI partner; } public enum WebSocketMessageType { - CONNECTION_REQUEST, - CREDENTIAL, - PARTNER, - PROOF, - PROOFREQUEST, - NOTIFICATION, - MESSAGE, - onCredentialAdded, - onPartnerRequestReceived, - onPartnerAdded, - onPartnerAccepted, - onPartnerRemoved, - onPresentationVerified, - onPresentationProved, - onPresentationRequestReceived, - onPresentationRequestSent, - onNewTask - } - - public enum WebSocketMessageState { - RECEIVED, - UPDATED, - SENT, - NEW, - COMPLETED - } - - public static WebSocketMessageBody partnerReceived(PartnerAPI partner) { - return WebSocketMessageBody.of(WebSocketMessage - .builder() - .type(WebSocketMessageType.PARTNER) - .state(WebSocketMessageState.RECEIVED) - .linkId(partner.getId()) - .info(partner) - .build()); - } - - public static WebSocketMessageBody credentialReceived(AriesCredential credential) { - return WebSocketMessageBody.of(WebSocketMessage - .builder() - .type(WebSocketMessageType.CREDENTIAL) - .state(WebSocketMessageState.RECEIVED) - .linkId(credential.getId().toString()) - .info(credential) - .build()); - } - - public static WebSocketMessageBody proof( - @NonNull WebSocketMessageState state, - @NonNull WebSocketMessageType type, - @NonNull AriesProofExchange proof) { - return WebSocketMessageBody.of(WebSocketMessage - .builder() - .type(type) - .state(state) - .linkId(proof.getId().toString()) - .info(proof) - .build()); - } - - public static WebSocketMessageBody notification(Object info, Boolean completed) { - return WebSocketMessageBody.of(WebSocketMessage - .builder() - .type(WebSocketMessageType.NOTIFICATION) - .state(completed ? WebSocketMessageState.COMPLETED : WebSocketMessageState.NEW) - .info(info) - .build()); + ON_MESSAGE_RECEIVED, + ON_CREDENTIAL_ADDED, + ON_PARTNER_REQUEST_COMPLETED, + ON_PARTNER_REQUEST_RECEIVED, + ON_PARTNER_ADDED, + ON_PARTNER_ACCEPTED, + ON_PARTNER_REMOVED, + ON_PRESENTATION_VERIFIED, + ON_PRESENTATION_PROVED, + ON_PRESENTATION_REQUEST_DECLINED, + ON_PRESENTATION_REQUEST_DELETED, + ON_PRESENTATION_REQUEST_RECEIVED, + ON_PRESENTATION_REQUEST_SENT, + TASK_ADDED, + TASK_COMPLETED } public static WebSocketMessageBody message(PartnerAPI partner, BasicMessage message) { - return WebSocketMessageBody.of(WebSocketMessage - .builder() - .type(WebSocketMessageType.MESSAGE) - .state(WebSocketMessageState.RECEIVED) - .info(PartnerMessage.builder() + return notificationEvent(WebSocketMessageType.ON_MESSAGE_RECEIVED, + partner.getId(), + PartnerMessage.builder() .partnerId(partner.getId()) .messageId(message.getMessageId()) .content(message.getContent()) - .build()) - .build()); + .build(), + partner); } public static WebSocketMessageBody notificationEvent(@NonNull WebSocketMessageType type, @@ -141,7 +84,6 @@ public static WebSocketMessageBody notificationEvent(@NonNull WebSocketMessageTy return WebSocketMessageBody.of(WebSocketMessage .builder() .type(type) - .state(WebSocketMessageState.SENT) .info(info) .partner(partner) .linkId(linkId) diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java index cfd08c644..a5a609242 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivityItem.java @@ -29,6 +29,7 @@ @Builder public class ActivityItem { + private String id; private ActivityRole role; private ActivityState state; private ActivityType type; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java index b1976270e..c6aee316d 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/ActivityManager.java @@ -17,12 +17,15 @@ */ package org.hyperledger.bpa.impl; +import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.annotation.NonNull; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hyperledger.aries.api.present_proof.PresentationExchangeRole; import org.hyperledger.bpa.config.ActivityLogConfig; import org.hyperledger.bpa.controller.api.activity.*; +import org.hyperledger.bpa.impl.notification.TaskAddedEvent; +import org.hyperledger.bpa.impl.notification.TaskCompletedEvent; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.model.Activity; import org.hyperledger.bpa.model.Partner; @@ -30,6 +33,7 @@ import org.hyperledger.bpa.repository.ActivityRepository; import org.hyperledger.bpa.repository.PartnerProofRepository; import org.hyperledger.bpa.repository.PartnerRepository; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.util.ArrayList; @@ -56,6 +60,9 @@ public class ActivityManager { @Inject Converter converter; + @Inject + ApplicationEventPublisher eventPublisher; + public List getItems(ActivitySearchParameters parameters) { List activities = new ArrayList<>(); @@ -99,6 +106,7 @@ public void addPartnerRequestReceivedTask(@NonNull Partner partner) { .completed(false) .build(); activityRepository.save(a); + eventPublisher.publishEventAsync(TaskAddedEvent.builder().activity(a).build()); } } @@ -110,6 +118,7 @@ public void completePartnerRequestTask(@NonNull Partner partner) { activity.setState(ActivityState.CONNECTION_REQUEST_ACCEPTED); activity.setCompleted(true); activityRepository.update(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); }, () -> { // add in a completed activity Activity a = Activity.builder() @@ -169,9 +178,7 @@ public void addPartnerAcceptedActivity(@NonNull Partner partner) { public void addPresentationExchangeTask(@NonNull PartnerProof partnerProof) { partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { // in case event is fired multiple times, see if already exists. - ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) - ? ActivityRole.PRESENTATION_EXCHANGE_PROVER - : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + ActivityRole role = getPresentationExchangeRole(partnerProof); ActivityState state = getPresentationExchangeState(partnerProof); Optional existing = activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), @@ -188,6 +195,11 @@ public void addPresentationExchangeTask(@NonNull PartnerProof partnerProof) { .completed(ActivityState.PRESENTATION_EXCHANGE_SENT.equals(state)) .build(); activityRepository.save(a); + + if (!a.isCompleted()) { + // this looks like we created a task! + eventPublisher.publishEventAsync(TaskAddedEvent.builder().activity(a).build()); + } } }); } @@ -195,9 +207,7 @@ public void addPresentationExchangeTask(@NonNull PartnerProof partnerProof) { public void completePresentationExchangeTask(@NonNull PartnerProof partnerProof) { partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { // in case event is fired multiple times, see if already exists. - ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) - ? ActivityRole.PRESENTATION_EXCHANGE_PROVER - : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + ActivityRole role = getPresentationExchangeRole(partnerProof); ActivityState state = ActivityState.PRESENTATION_EXCHANGE_ACCEPTED; activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), @@ -207,6 +217,8 @@ public void completePresentationExchangeTask(@NonNull PartnerProof partnerProof) activity.setState(state); activity.setCompleted(true); activityRepository.update(activity); + + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); }, () -> { // add in a completed activity Activity a = Activity.builder() @@ -225,9 +237,7 @@ public void completePresentationExchangeTask(@NonNull PartnerProof partnerProof) public void declinePresentationExchangeTask(@NonNull PartnerProof partnerProof) { partnerRepo.findById(partnerProof.getPartnerId()).ifPresent(partner -> { // in case event is fired multiple times, see if already exists. - ActivityRole role = PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) - ? ActivityRole.PRESENTATION_EXCHANGE_PROVER - : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + ActivityRole role = getPresentationExchangeRole(partnerProof); activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE, @@ -235,16 +245,24 @@ public void declinePresentationExchangeTask(@NonNull PartnerProof partnerProof) activity.setState(ActivityState.PRESENTATION_EXCHANGE_DECLINED); activity.setCompleted(true); activityRepository.update(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); }); }); } public void deletePresentationExchangeTask(@NonNull PartnerProof partnerProof) { - activityRepository.deleteByLinkIdAndType(partnerProof.getId(), ActivityType.PRESENTATION_EXCHANGE); + ActivityRole role = getPresentationExchangeRole(partnerProof); + activityRepository.findByLinkIdAndTypeAndRole(partnerProof.getId(), + ActivityType.PRESENTATION_EXCHANGE, + role).ifPresent(activity -> { + activityRepository.delete(activity); + eventPublisher.publishEventAsync(TaskCompletedEvent.builder().activity(activity).build()); + }); } private ActivityItem convert(Activity activity) { return ActivityItem.builder() + .id(activity.getId().toString()) .linkId(activity.getLinkId().toString()) .partner(converter.toAPIObject(activity.getPartner())) .role(activity.getRole()) @@ -277,4 +295,11 @@ private ActivityState getPresentationExchangeState(PartnerProof partnerProof) { } } + @NotNull + private ActivityRole getPresentationExchangeRole(@NonNull PartnerProof partnerProof) { + return PresentationExchangeRole.PROVER.equals(partnerProof.getRole()) + ? ActivityRole.PRESENTATION_EXCHANGE_PROVER + : ActivityRole.PRESENTATION_EXCHANGE_VERIFIER; + } + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java index 3b824e182..4cd4d3457 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/NotificationEventListener.java @@ -61,7 +61,7 @@ public void onCredentialAddedEvent(CredentialAddedEvent event) { PartnerAPI partnerAPI = partnerManager.getPartnerByConnectionId(event.getCredential().getConnectionId()); if (partnerAPI != null) { WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onCredentialAdded, + WebSocketMessageBody.WebSocketMessageType.ON_CREDENTIAL_ADDED, event.getCredential().getId().toString(), event.getCredential(), partnerAPI); @@ -73,6 +73,13 @@ public void onCredentialAddedEvent(CredentialAddedEvent event) { @Async public void onPartnerRequestCompletedEvent(PartnerRequestCompletedEvent event) { log.debug("onPartnerRequestCompletedEvent"); + WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.ON_PARTNER_REQUEST_COMPLETED, + event.getPartner().getId().toString(), + null, + conv.toAPIObject(event.getPartner())); + messageService.sendMessage(message); + activityManager.completePartnerRequestTask(event.getPartner()); } @@ -86,7 +93,7 @@ public void onPartnerRequestReceivedEvent(PartnerRequestReceivedEvent event) { activityManager.addPartnerRequestReceivedTask(event.getPartner()); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPartnerRequestReceived, + WebSocketMessageBody.WebSocketMessageType.ON_PARTNER_REQUEST_RECEIVED, event.getPartner().getId().toString(), null, conv.toAPIObject(event.getPartner())); @@ -99,7 +106,7 @@ public void onPartnerRequestReceivedEvent(PartnerRequestReceivedEvent event) { public void onPartnerAddedEvent(PartnerAddedEvent event) { log.debug("onPartnerAddedEvent"); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPartnerAdded, + WebSocketMessageBody.WebSocketMessageType.ON_PARTNER_ADDED, event.getPartner().getId().toString(), null, conv.toAPIObject(event.getPartner())); @@ -113,7 +120,7 @@ public void onPartnerAddedEvent(PartnerAddedEvent event) { public void onPartnerAcceptedEvent(PartnerAcceptedEvent event) { log.debug("onPartnerAcceptedEvent"); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPartnerAccepted, + WebSocketMessageBody.WebSocketMessageType.ON_PARTNER_ACCEPTED, event.getPartner().getId().toString(), null, conv.toAPIObject(event.getPartner())); @@ -127,7 +134,7 @@ public void onPartnerAcceptedEvent(PartnerAcceptedEvent event) { public void onPartnerRemovedEvent(PartnerRemovedEvent event) { log.debug("onPartnerRemovedEvent"); WebSocketMessageBody message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPartnerRemoved, + WebSocketMessageBody.WebSocketMessageType.ON_PARTNER_REMOVED, event.getPartner().getId().toString(), null, conv.toAPIObject(event.getPartner())); @@ -145,13 +152,13 @@ public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEven WebSocketMessageBody message = null; if (event.getPartnerProof().getRole().equals(PresentationExchangeRole.PROVER)) { message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPresentationProved, + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_PROVED, event.getPartnerProof().getId().toString(), conv.toAPIObject(event.getPartnerProof()), p); } else { message = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onPresentationVerified, + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_VERIFIED, event.getPartnerProof().getId().toString(), conv.toAPIObject(event.getPartnerProof()), p); @@ -165,6 +172,8 @@ public void onPresentationRequestCompletedEvent(PresentationRequestCompletedEven @Async public void onPresentationRequestDeclinedEvent(PresentationRequestDeclinedEvent event) { log.debug("onPresentationRequestDeclinedEvent"); + handlePresentationRequestEvent(event.getPartnerProof(), + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_REQUEST_DECLINED); activityManager.declinePresentationExchangeTask(event.getPartnerProof()); } @@ -172,6 +181,8 @@ public void onPresentationRequestDeclinedEvent(PresentationRequestDeclinedEvent @Async public void onPresentationRequestDeletedEvent(PresentationRequestDeletedEvent event) { log.debug("onPresentationRequestDeletedEvent"); + handlePresentationRequestEvent(event.getPartnerProof(), + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_REQUEST_DELETED); activityManager.deletePresentationExchangeTask(event.getPartnerProof()); } @@ -180,7 +191,7 @@ public void onPresentationRequestDeletedEvent(PresentationRequestDeletedEvent ev public void onPresentationRequestReceivedEvent(PresentationRequestReceivedEvent event) { log.debug("onPresentationRequestReceivedEvent"); handlePresentationRequestEvent(event.getPartnerProof(), - WebSocketMessageBody.WebSocketMessageType.onPresentationRequestReceived); + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_REQUEST_RECEIVED); } @EventListener @@ -188,7 +199,7 @@ public void onPresentationRequestReceivedEvent(PresentationRequestReceivedEvent public void onPresentationRequestSentEvent(PresentationRequestSentEvent event) { log.debug("onPresentationRequestSentEvent"); handlePresentationRequestEvent(event.getPartnerProof(), - WebSocketMessageBody.WebSocketMessageType.onPresentationRequestSent); + WebSocketMessageBody.WebSocketMessageType.ON_PRESENTATION_REQUEST_SENT); } @EventListener @@ -203,6 +214,30 @@ public void onBasicMessageReceivedEvent(BasicMessageReceivedEvent event) { } } + @EventListener + @Async + public void onTaskAddedEvent(TaskAddedEvent event) { + log.debug("onTaskAddedEvent"); + WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.TASK_ADDED, + event.getActivity().getId().toString(), + event.getActivity(), + conv.toAPIObject(event.getActivity().getPartner())); + messageService.sendMessage(task); + } + + @EventListener + @Async + public void onTaskCompletedEvent(TaskCompletedEvent event) { + log.debug("onTaskCompletedEvent"); + WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( + WebSocketMessageBody.WebSocketMessageType.TASK_COMPLETED, + event.getActivity().getId().toString(), + event.getActivity(), + conv.toAPIObject(event.getActivity().getPartner())); + messageService.sendMessage(task); + } + private void handlePresentationRequestEvent(@NonNull PartnerProof partnerProof, WebSocketMessageBody.WebSocketMessageType messageType) { Optional partnerAPI = partnerManager.getPartnerById(partnerProof.getPartnerId()); @@ -220,13 +255,6 @@ private void handlePresentationRequestEvent(@NonNull PartnerProof partnerProof, conv.toAPIObject(partnerProof), p); messageService.sendMessage(message); - - WebSocketMessageBody task = WebSocketMessageBody.notificationEvent( - WebSocketMessageBody.WebSocketMessageType.onNewTask, - partnerProof.getId().toString(), - conv.toAPIObject(partnerProof), - p); - messageService.sendMessage(task); } } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java index 146c28325..e43a84ad9 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/CredentialManager.java @@ -263,7 +263,7 @@ public void deleteCredentialById(@NonNull UUID id) { * * @param ariesCred {@link Credential} * @return the issuer or null when the credential or the credential definition - * id is null + * id is null */ @Nullable String resolveIssuer(@Nullable Credential ariesCred) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskAddedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskAddedEvent.java new file mode 100644 index 000000000..4c4a44d15 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskAddedEvent.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.Activity; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class TaskAddedEvent { + private Activity activity; +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskCompletedEvent.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskCompletedEvent.java new file mode 100644 index 000000000..e6a4d7702 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/notification/TaskCompletedEvent.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.notification; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hyperledger.bpa.model.Activity; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class TaskCompletedEvent { + private Activity activity; +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2e06f5919..014b67839 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -67,8 +67,8 @@ - $vuetify.icons.partners + + $vuetify.icons.partners + {{ $t("nav.partners") }} @@ -223,8 +233,8 @@ + @@ -78,10 +84,11 @@ import VBpaButton from "@/components/BpaButton"; import activitiesService from "@/services/activitiesService"; import * as partnerUtils from "@/utils/partnerUtils"; + import NewMessageIcon from "@/components/NewMessageIcon"; export default { name: "ActivityList", - components: { VBpaButton }, + components: { VBpaButton, NewMessageIcon }, props: { activities: { type: Boolean, @@ -94,6 +101,12 @@ headers: { type: Array, default: () => [ + { + text: '', + value: 'indicator', + sortable: false, + filterable: false + }, { text: "Type", value: "type", @@ -165,6 +178,9 @@ }); }, openItem(item) { + // if we click on it, mark it seen... + this.$store.commit("taskNotificationSeen", {id: item.id}); + if (item.type === ActivityTypes.CONNECTION_REQUEST.value) { this.$router.push({ name: "Partner", diff --git a/frontend/src/components/CredExList.vue b/frontend/src/components/CredExList.vue index fbd7350eb..e0b5010fe 100644 --- a/frontend/src/components/CredExList.vue +++ b/frontend/src/components/CredExList.vue @@ -18,6 +18,12 @@ single-select @click:row="openItem" > +