From f3dbf61d66ab0ac7e9f4a9a3d7d06cd735684a15 Mon Sep 17 00:00:00 2001 From: Timo Notheisen <65653426+tnotheis@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:38:17 +0200 Subject: [PATCH] Queueing of external events for new messages while relationship is in status `Terminated` (#912) * feat: allow sending messages in status Terminated * refactor: remove reference to Newtonsoft.Json from ConsumerApi.Sdk * chore: delete leftover packages.lock.json file * refactor: rename RelationshipReactivationRequest method to ReactivateRelationship * refactor: fix typo in ExternalEventIdPayloadEntityFrameworkValueConverter * refactor: make external event payloads strongly typed * refactor: add empty constructor to each ExternalEvent subclass to satisfy ArchUnit tests * chore: fix formatting * feat: modify setup.sql scripts to allow synchronization user access to Relationships schema * test: add integration tests * test: add more integration tests * feat: add RelationshipId to message recipient * feat: propagate RelationshipId via DomainEvent * feat: add migrations for adding the IsDeliveryBlocked and Context columns to the ExternalEvents table * test: delete unused BaseStepDefinitions * test: fix reactivation by accepting as peer * feat: send NewRelationshipStatus as part of RelationshipReactivationCompletedDomainEvent * feat: update entity type configurations * feat: update MessageCreatedDomainEventHandler to block external events if the relationship is terminated * feat: unblock external events after relationship was reactivated * chore: remove todo list * test: remove Task.Delay * chore: add explaining comments to custom SQL in migrations * test: fix test * refactor: extract deletion of external events to method in dbcontext * test: fix tests * test: add some Task.Delays to avoid failing tests * fix: use correct type param for logger * test: add tests for MessageCreatedDomainEventHandler * chore: remove reactivation part from error message * test: add tests for RelationshipReactivationCompletedDomainEventHandler * test: add tests for RelationshipStatusChangedDomainEventHandler * test: add tests for Relationship * chore: rename ImplicitUsings to GlobalUsings to better reflect what they contain * test: inherit from AbstractTestsBase * chore: fix formatting * feat: don't send a push notification for blocked external events --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../Features/Messages/GET.feature | 7 + .../Features/Messages/POST.feature | 6 + .../SyncRuns/{id}/ExternalEvents/GET.feature | 45 ++ .../Helpers/Utils.cs | 2 +- .../StepDefinitions/BaseStepDefinitions.cs | 39 -- .../StepDefinitions/CommonStepDefinitions.cs | 17 + .../RelationshipsStepDefinitions.cs | 16 +- .../SynchronizationStepDefinitions.cs | 73 +++ .../tools/snapshot-creator/packages.lock.json | 463 ------------------ .../ActualDeletionWorkerTests.cs | 3 +- .../Crypto/CryptoSignaturePublicKey.cs | 4 +- .../ExternalEventCreatedDomainEventHandler.cs | 3 + .../ExternalEventCreatedDomainEvent.cs | 1 + ...rnalEventCreatedDomainEventHandlerTests.cs | 44 ++ .../Messages/Commands/SendMessage/Handler.cs | 2 +- .../src/Messages.Domain/DomainErrors.cs | 2 +- .../Outgoing/MessageCreatedDomainEvent.cs | 14 +- .../Entities/RecipientInformation.cs | 5 +- .../Messages.Domain/Entities/Relationship.cs | 2 +- ...onshipIdToRecipientInformation.Designer.cs | 209 ++++++++ ...AddRelationshipIdToRecipientInformation.cs | 48 ++ .../MessagesDbContextModelSnapshot.cs | 9 +- ...onshipIdToRecipientInformation.Designer.cs | 208 ++++++++ ...AddRelationshipIdToRecipientInformation.cs | 48 ++ .../MessagesDbContextModelSnapshot.cs | 12 +- ...pientInformationEntityTypeConfiguration.cs | 1 + .../TestHelpers/TestData.cs | 6 +- .../Messages/AnonymizeParticipantTests.cs | 3 +- .../Messages/CreationTests.cs | 7 +- ...=> EnsureSendingMessagesIsAllowedTests.cs} | 10 +- .../TestHelpers/TestData.cs | 6 +- .../MessageCreatedDomainEvent.cs | 2 - ...ionshipReactivationCompletedDomainEvent.cs | 3 + .../MessageCreatedDomainEventHandler.cs | 36 +- ...ReactivationCompletedDomainEventHandler.cs | 17 + ...ReactivationRequestedDomainEventHandler.cs | 5 +- ...tionshipStatusChangedDomainEventHandler.cs | 11 + .../ExternalEventsQueryableExtensions.cs | 20 + .../IRelationshipsRepository.cs | 8 + .../ISynchronizationDbContext.cs | 2 + .../MessageCreatedDomainEvent.cs | 9 +- ...ionshipReactivationCompletedDomainEvent.cs | 12 +- .../RelationshipStatusChangedDomainEvent.cs | 1 + .../ExternalEventCreatedDomainEvent.cs | 2 + .../Entities/Relationships/Relationship.cs | 25 + .../Entities/Relationships/RelationshipId.cs | 47 ++ .../Relationships/RelationshipStatus.cs | 12 + .../Entities/Sync/ExternalEvent.cs | 17 +- .../Sync/MessageReceivedExternalEvent.cs | 5 +- ...xtColumnsToExternalEventsTable.Designer.cs | 341 +++++++++++++ ...dAndContextColumnsToExternalEventsTable.cs | 44 ++ .../SynchronizationDbContextModelSnapshot.cs | 28 +- ...xtColumnsToExternalEventsTable.Designer.cs | 341 +++++++++++++ ...dAndContextColumnsToExternalEventsTable.cs | 44 ++ .../SynchronizationDbContextModelSnapshot.cs | 28 +- .../ExternalEventEntityTypeConfiguration.cs | 4 + .../RelationshipEntityTypeConfiguration.cs | 19 + .../Database/SynchronizationDbContext.cs | 24 + ...entPayloadEntityFrameworkValueConverter.cs | 4 +- ...tionshipIdEntityFrameworkValueConverter.cs | 20 + .../IServiceCollectionExtensions.cs | 2 + .../IServiceCollectionExtensions.cs | 12 + .../Repository/RelationshipsRepository.cs | 21 + .../ExternalEventBuilder.cs | 3 +- .../GlobalUsings.cs | 4 + .../MessageCreatedDomainEventHandlerTests.cs | 121 +++++ ...ivationCompletedDomainEventHandlerTests.cs | 50 +- ...hipStatusChangedDomainEventHandlerTests.cs | 34 +- .../GlobalUsings.cs | 4 + .../RelationshipTests.cs | 40 ++ Sdks/ConsumerApi.Sdk/src/Client.cs | 8 +- .../src/ConsumerApi.Sdk.csproj | 5 - .../Relationships/RelationshipsEndpoint.cs | 2 +- .../Endpoints/SyncRuns/Types/ExternalEvent.cs | 6 +- scripts/sql/postgres/setup.sql | 4 + scripts/sql/sqlserver/setup.sql | 1 + 76 files changed, 2188 insertions(+), 575 deletions(-) create mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/SyncRuns/{id}/ExternalEvents/GET.feature delete mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/BaseStepDefinitions.cs create mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/CommonStepDefinitions.cs create mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/SynchronizationStepDefinitions.cs delete mode 100644 Applications/ConsumerApi/test/ConsumerApi.Tests.Performance/tools/snapshot-creator/packages.lock.json create mode 100644 Modules/Devices/test/Devices.Application.Tests/Tests/DomainEvents/Incoming/ExternalEventCreatedDomainEventHandlerTests.cs create mode 100644 Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.Designer.cs create mode 100644 Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.cs create mode 100644 Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.Designer.cs create mode 100644 Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.cs rename Modules/Messages/test/Messages.Domain.Tests/Relationships/{RelationshipTests.cs => EnsureSendingMessagesIsAllowedTests.cs} (83%) create mode 100644 Modules/Synchronization/src/Synchronization.Application/Infrastructure/IRelationshipsRepository.cs create mode 100644 Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/Relationship.cs create mode 100644 Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipId.cs create mode 100644 Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipStatus.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/RelationshipEntityTypeConfiguration.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/RelationshipIdEntityFrameworkValueConverter.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Repository/IServiceCollectionExtensions.cs create mode 100644 Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Repository/RelationshipsRepository.cs create mode 100644 Modules/Synchronization/test/Synchronization.Application.Tests/GlobalUsings.cs create mode 100644 Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/MessageCreatedDomainEventHandlerTests.cs create mode 100644 Modules/Synchronization/test/Synchronization.Domain.Tests/GlobalUsings.cs create mode 100644 Modules/Synchronization/test/Synchronization.Domain.Tests/RelationshipTests.cs diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/GET.feature b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/GET.feature index 233b3ae292..edb087979f 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/GET.feature +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/GET.feature @@ -19,6 +19,7 @@ Identity gets all its Messages And i1 has sent a Message m to i2 and i3 And i1 has terminated r12 And i1 has decomposed r12 + And 1 second(s) have passed When i1 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response contains the Message m @@ -31,6 +32,7 @@ Identity gets all its Messages And i1 has sent a Message m to i2 and i3 And i2 has terminated r12 And i2 has decomposed r12 + And 1 second(s) have passed When i1 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response contains the Message m @@ -43,6 +45,7 @@ Identity gets all its Messages And i1 has terminated r12 And i1 has decomposed r12 And i2 has decomposed r12 + And 1 second(s) have passed When i1 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response contains the Message m @@ -57,6 +60,7 @@ Identity gets all its Messages And i1 has decomposed r12 And i1 has terminated r13 And i1 has decomposed r13 + And 1 second(s) have passed When i1 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response does not contain the Message m @@ -68,6 +72,7 @@ Identity gets all its Messages And i1 has sent a Message m to i2 and i3 And i2 has terminated r12 And i2 has decomposed r12 + And 1 second(s) have passed When i2 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response does not contain the Message m @@ -80,6 +85,7 @@ Identity gets all its Messages And i1 has terminated r12 And i1 has decomposed r12 And i2 has decomposed r12 + And 1 second(s) have passed When i3 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response contains the Message m @@ -95,6 +101,7 @@ Identity gets all its Messages And i1 has terminated r13 And i1 has decomposed r13 And i3 has decomposed r13 + And 1 second(s) have passed When i1 sends a GET request to the /Messages endpoint Then the response status code is 200 (Ok) And the response does not contain the Message m diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/POST.feature b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/POST.feature index 2c3a00a553..4695759f93 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/POST.feature +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/Messages/POST.feature @@ -18,3 +18,9 @@ Identity sends a Message Then the response status code is 400 (Bad Request) And the response content contains an error with the error code "error.platform.validation.message.recipientToBeDeleted" And the error contains a list of Identities to be deleted that includes i2 + + Scenario: Sending a Message to a terminated Relationship + Given Identities i1 and i2 + And a terminated Relationship r between i1 and i2 + When i1 sends a POST request to the /Messages endpoint with i2 as recipient + Then the response status code is 201 (Created) diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/SyncRuns/{id}/ExternalEvents/GET.feature b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/SyncRuns/{id}/ExternalEvents/GET.feature new file mode 100644 index 0000000000..ee650e669b --- /dev/null +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Features/SyncRuns/{id}/ExternalEvents/GET.feature @@ -0,0 +1,45 @@ +@Integration +Feature: GET /SyncRuns/{id}/ExternalEvents + + Scenario: Getting external events does not return events for messages sent while the Relationship is still terminated + Given Identities i1 and i2 + And a terminated Relationship r between i1 and i2 + And i1 has sent a Message m to i2 + And a sync run sr started by i2 + When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint + Then the response status code is 200 (OK) + And the response does not contain an external event for the Message m + + Scenario: Getting external events returns events for messages sent while the Relationship was terminated + Given Identities i1 and i2 + And a terminated Relationship r between i1 and i2 + And i1 has sent a Message m to i2 + And r was fully reactivated + And 1 second(s) have passed + And a sync run sr started by i2 + When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint + Then the response status code is 200 (OK) + Then the response contains an external event for the Message m + + Scenario: Getting external events returns events for messages sent before the Relationship was terminated + Given Identities i1 and i2 + And an active Relationship r between i1 and i2 + And i1 has sent a Message m to i2 + And i1 has terminated r + And a sync run sr started by i2 + When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint + Then the response status code is 200 (OK) + Then the response contains an external event for the Message m + + Scenario: Getting external events does not return events for messages that were sent with an old Relationship + Given Identities i1 and i2 + And an active Relationship r between i1 and i2 + And i1 has sent a Message m to i2 + And i1 has terminated r + And i1 has decomposed r + And i2 has decomposed r + And an active Relationship r between i1 and i2 + And a sync run sr started by i2 + When i2 sends a GET request to the /SyncRuns/sr.id/ExternalEvents endpoint + Then the response status code is 200 (OK) + Then the response does not contain an external event for the Message m diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs index 3d63d52d5a..92031662b8 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/Helpers/Utils.cs @@ -104,7 +104,7 @@ public static async Task CreateTerminatedRelationshipWithReactivat { var relationshipMetadata = await CreateTerminatedRelationshipBetween(client1, client2); - var reactivateRelationshipResponse = await client2.Relationships.RelationshipReactivationRequest(relationshipMetadata.Id); + var reactivateRelationshipResponse = await client2.Relationships.ReactivateRelationship(relationshipMetadata.Id); reactivateRelationshipResponse.Should().BeASuccess(); var getRelationshipResponse = await client2.Relationships.GetRelationship(relationshipMetadata.Id); diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/BaseStepDefinitions.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/BaseStepDefinitions.cs deleted file mode 100644 index e74724689b..0000000000 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/BaseStepDefinitions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Backbone.ConsumerApi.Sdk; -using Backbone.ConsumerApi.Sdk.Authentication; -using Backbone.ConsumerApi.Tests.Integration.Configuration; -using Backbone.ConsumerApi.Tests.Integration.Support; -using Microsoft.Extensions.Options; - -namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions; - -internal class BaseStepDefinitions -{ - internal readonly Dictionary Identities = new(); - internal readonly HttpClient HttpClient; - internal readonly ClientCredentials ClientCredentials; - - public BaseStepDefinitions(HttpClientFactory factory, IOptions httpConfiguration) - { - HttpClient = factory.CreateClient(); - ClientCredentials = new ClientCredentials(httpConfiguration.Value.ClientCredentials.ClientId, httpConfiguration.Value.ClientCredentials.ClientSecret); - } - - #region Given - - [Given(@"Identities (i[a-zA-Z0-9]*) and (i[a-zA-Z0-9]*)")] - public void Given2Identities(string identity1Name, string identity2Name) - { - Identities[identity1Name] = Client.CreateForNewIdentity(HttpClient, ClientCredentials, Constants.DEVICE_PASSWORD).Result; - Identities[identity2Name] = Client.CreateForNewIdentity(HttpClient, ClientCredentials, Constants.DEVICE_PASSWORD).Result; - } - - [Given(@"Identities (i[a-zA-Z0-9]*), (i[a-zA-Z0-9]*) and (i[a-zA-Z0-9]*)")] - public void Given3Identities(string identity1Name, string identity2Name, string identity3Name) - { - Identities[identity1Name] = Client.CreateForNewIdentity(HttpClient, ClientCredentials, Constants.DEVICE_PASSWORD).Result; - Identities[identity2Name] = Client.CreateForNewIdentity(HttpClient, ClientCredentials, Constants.DEVICE_PASSWORD).Result; - Identities[identity3Name] = Client.CreateForNewIdentity(HttpClient, ClientCredentials, Constants.DEVICE_PASSWORD).Result; - } - - #endregion -} diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/CommonStepDefinitions.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/CommonStepDefinitions.cs new file mode 100644 index 0000000000..af4731165c --- /dev/null +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/CommonStepDefinitions.cs @@ -0,0 +1,17 @@ +using FluentAssertions.Extensions; + +namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions; + +[Binding] +internal class CommonStepDefinitions +{ + #region Given + + [Given(@"(\d+) second\(s\) have passed")] + public async Task GivenXSecondsHavePassed(int numberOfSeconds) + { + await Task.Delay(numberOfSeconds.Seconds()); + } + + #endregion +} diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs index 2b2dee4fa0..e5a66839cb 100644 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/RelationshipsStepDefinitions.cs @@ -83,6 +83,18 @@ public async Task GivenATerminatedRelationshipWithReactivationRequest(string rel _relationshipsContext.Relationships[relationshipName] = await Utils.CreateTerminatedRelationshipWithReactivationRequestBetween(participant1, participant2); } + [Given($"{RegexFor.SINGLE_THING} was fully reactivated")] + public async Task GivenRWasFullyReactivated(string relationshipName) + { + var relationship = _relationshipsContext.Relationships[relationshipName]; + + var clientFrom = _clientPool.FirstForIdentityAddress(relationship.From); + await clientFrom.Relationships.ReactivateRelationship(relationship.Id); + + var clientTo = _clientPool.FirstForIdentityAddress(relationship.To); + await clientTo.Relationships.AcceptReactivationOfRelationship(relationship.Id); + } + [Given($"{RegexFor.SINGLE_THING} has terminated {RegexFor.SINGLE_THING}")] public async Task GivenRelationshipIsTerminated(string terminatorName, string relationshipName) { @@ -100,8 +112,6 @@ public async Task GivenIdentityHasDecomposedItsRelationshipToIdentity(string dec var response = await decomposer.Relationships.DecomposeRelationship(relationship.Id); response.Should().BeASuccess(); - - await Task.Delay(500); } #endregion @@ -148,7 +158,7 @@ public async Task WhenIdentitySendsAPutRequestToTheRelationshipsIdReactivateEndp { var client = _clientPool.FirstForIdentityName(identityName); - _responseContext.WhenResponse = await client.Relationships.RelationshipReactivationRequest(_relationshipsContext.Relationships[relationshipName].Id); + _responseContext.WhenResponse = await client.Relationships.ReactivateRelationship(_relationshipsContext.Relationships[relationshipName].Id); } [When($"{RegexFor.SINGLE_THING} sends a PUT request to the /Relationships/{{{RegexFor.SINGLE_THING}.Id}}/Reactivate/(Accept|Reject|Revoke) endpoint")] diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/SynchronizationStepDefinitions.cs b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/SynchronizationStepDefinitions.cs new file mode 100644 index 0000000000..fb047b373f --- /dev/null +++ b/Applications/ConsumerApi/test/ConsumerApi.Tests.Integration/StepDefinitions/SynchronizationStepDefinitions.cs @@ -0,0 +1,73 @@ +using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; +using Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types.Requests; +using Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types.Responses; +using Backbone.ConsumerApi.Tests.Integration.Contexts; +using Backbone.ConsumerApi.Tests.Integration.Helpers; + +namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions; + +[Binding] +internal class SynchronizationStepDefinitions +{ + private readonly ClientPool _clientPool; + private readonly Dictionary _startSyncRunResponses = new(); + private readonly ResponseContext _responseContext; + private readonly MessagesContext _messagesContext; + private ApiResponse? _listExternalEventsOfSyncRunResponse; + + public SynchronizationStepDefinitions(ResponseContext responseContext, MessagesContext messagesContext, ClientPool clientPool) + { + _responseContext = responseContext; + _messagesContext = messagesContext; + _clientPool = clientPool; + } + + #region Given + + [Given($"a sync run {RegexFor.SINGLE_THING} started by {RegexFor.SINGLE_THING}")] + public async Task GivenASyncRunStartedBy(string syncRunName, string identityName) + { + var client = _clientPool.FirstForIdentityName(identityName); + + var startSyncRunResponse = await client.SyncRuns.StartSyncRun(new StartSyncRunRequest { Type = SyncRunType.ExternalEventSync }, 1); + + _startSyncRunResponses.Add(syncRunName, startSyncRunResponse.Result!); + } + + #endregion + + #region When + + [When($"{RegexFor.SINGLE_THING} sends a GET request to the /SyncRuns/{RegexFor.SINGLE_THING}.id/ExternalEvents endpoint")] + public async Task WhenISendsAGETRequestToTheSyncRunsSrIdExternalEventsEndpoint(string identityName, string syncRunName) + { + var client = _clientPool.FirstForIdentityName(identityName); + + var syncRunId = _startSyncRunResponses[syncRunName].SyncRun.Id; + + _responseContext.WhenResponse = _listExternalEventsOfSyncRunResponse = await client.SyncRuns.ListExternalEventsOfSyncRun(syncRunId); + } + + #endregion + + #region Then + + [Then($"the response does not contain an external event for the Message {RegexFor.SINGLE_THING}")] + public void ThenTheResponseDoesNotContainAnExternalEventForM(string _) + { + _listExternalEventsOfSyncRunResponse!.Result.Should().NotBeEmpty(); + _listExternalEventsOfSyncRunResponse.Result.Should().NotContain(e => e.Type == "MessageReceived"); + } + + [Then($"the response contains an external event for the Message {RegexFor.SINGLE_THING}")] + public void ThenTheResponseContainsAnExternalEventForM(string messageName) + { + var message = _messagesContext.Messages[messageName]; + _listExternalEventsOfSyncRunResponse!.Result.Should().NotBeEmpty(); + _listExternalEventsOfSyncRunResponse.Result.Should().ContainSingle(e => e.Type == "MessageReceived"); + var messageReceivedExternalEvent = _listExternalEventsOfSyncRunResponse.Result!.Single(e => e.Type == "MessageReceived"); + messageReceivedExternalEvent.Payload["id"].GetString().Should().Be(message.Id); + } + + #endregion +} diff --git a/Applications/ConsumerApi/test/ConsumerApi.Tests.Performance/tools/snapshot-creator/packages.lock.json b/Applications/ConsumerApi/test/ConsumerApi.Tests.Performance/tools/snapshot-creator/packages.lock.json deleted file mode 100644 index ca5dcbb656..0000000000 --- a/Applications/ConsumerApi/test/ConsumerApi.Tests.Performance/tools/snapshot-creator/packages.lock.json +++ /dev/null @@ -1,463 +0,0 @@ -{ - "version": 1, - "dependencies": { - "net8.0": { - "Microsoft.Extensions.DependencyInjection": { - "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" - } - }, - "System.CommandLine": { - "type": "Direct", - "requested": "[2.0.0-beta4.22272.1, )", - "resolved": "2.0.0-beta4.22272.1", - "contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==" - }, - "BouncyCastle.Cryptography": { - "type": "Transitive", - "resolved": "2.4.0", - "contentHash": "SwXsAV3sMvAU/Nn31pbjhWurYSjJ+/giI/0n6tCrYoupEK34iIHCuk3STAd9fx8yudM85KkLSVdn951vTng/vQ==" - }, - "FluentAssertions": { - "type": "Transitive", - "resolved": "6.12.1", - "contentHash": "hciWwryyLw3eonfqhFpOMTXyM1/auJChYslEBA+iGJyuBs5O3t/kA8YaeH4iRo/2Fe3ElSYL86C0miivtZ0f3g==", - "dependencies": { - "System.Configuration.ConfigurationManager": "4.4.0" - } - }, - "libsodium": { - "type": "Transitive", - "resolved": "1.0.19", - "contentHash": "tupm/HViwBN6Knd/gckR+cLaJGR39GLmiU4iDMM5hp/1BoczMr8fwJhSU+3/C2V4hi9nBK/4FICRKtTLU30TCA==" - }, - "Microsoft.CSharp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "P+MBhIM0YX+JqROuf7i306ZLJEjQYA9uUyRDE+OqwUI5sh41e2ZbPQV3LfAPh+29cmceE1pUffXsGfR4eMY3KA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" - } - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - }, - "Microsoft.NETCore.Targets": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" - }, - "Namotion.Reflection": { - "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "Qn0wM7u9TpSpja2x8UVexr2bLHb1DGMNhD2TCz3woklxaY1oH+Sitrw9fg/4YbNoNtczeH2jf+yPdXMQlgvFlQ==", - "dependencies": { - "Microsoft.CSharp": "4.3.0" - } - }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, - "NJsonSchema": { - "type": "Transitive", - "resolved": "11.0.2", - "contentHash": "BOgw+TOd1w7BSRIEWwkiSgHlKWC2eu0DHsSsb1LIwlC1Hq26A0ARZiMjsCsqfXqXdr7hLf1m4M84Z7LW1wmCGA==", - "dependencies": { - "NJsonSchema.Annotations": "11.0.2", - "Namotion.Reflection": "3.1.1", - "Newtonsoft.Json": "13.0.3" - } - }, - "NJsonSchema.Annotations": { - "type": "Transitive", - "resolved": "11.0.2", - "contentHash": "VbA0fmxVyqloGXYz863g6QHyojM1tgejwPQr9LjXdubs9YJt5YfRPCQOV/hnzpP2Bqd7nZFpDn9MCImmLAmqCw==" - }, - "NJsonSchema.NewtonsoftJson": { - "type": "Transitive", - "resolved": "11.0.2", - "contentHash": "tTVG8h7qfw6anxlhXGx3oUz7f3ig+t9jO3yrho73ypvMZ7DCyFtiaF5gG3GqBDZXM49ONogDmTZ+8HTG6AKaNQ==", - "dependencies": { - "NJsonSchema": "11.0.2", - "Newtonsoft.Json": "13.0.3" - } - }, - "NSec.Cryptography": { - "type": "Transitive", - "resolved": "24.4.0", - "contentHash": "R89OF0T5OY9QnPRoTsMCaPKMqtXcWKZSWAO4zyUaFHI+ZRMgXU7WaIwg/rPP3vOfdaLl8HdUHWkDB+2B9FOO7g==", - "dependencies": { - "libsodium": "[1.0.19, 1.0.20)" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Configuration.ConfigurationManager": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==", - "dependencies": { - "System.Security.Cryptography.ProtectedData": "4.4.0" - } - }, - "System.Diagnostics.Debug": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Linq.Expressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" - }, - "System.ObjectModel": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.ILGeneration": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.Lightweight": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Security.Cryptography.ProtectedData": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog==" - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "UrlBase64": { - "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "b8tTEqBi0IEHU03MMG8raeKjn4P5EiFYfbx9mIotiGNGhl18b7p8qX0So4pawR1kSYIJWGQVngeWXHMFzZgq4g==", - "dependencies": { - "System.Buffers": "4.5.0", - "System.Memory": "4.5.3" - } - }, - "Backbone.BuildingBlocks.SDK": { - "type": "Project", - "dependencies": { - "Backbone.Tooling": "[1.0.0, )", - "FluentAssertions": "[6.12.1, )", - "NJsonSchema.NewtonsoftJson": "[11.0.2, )" - } - }, - "Backbone.ConsumerApi.Sdk": { - "type": "Project", - "dependencies": { - "Backbone.BuildingBlocks.SDK": "[1.0.0, )", - "Backbone.Crypto": "[1.0.0, )", - "Backbone.Tooling": "[1.0.0, )", - "Newtonsoft.Json": "[13.0.3, )" - } - }, - "Backbone.Crypto": { - "type": "Project", - "dependencies": { - "BouncyCastle.Cryptography": "[2.4.0, )", - "NSec.Cryptography": "[24.4.0, )" - } - }, - "Backbone.Tooling": { - "type": "Project", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[8.0.2, )", - "UrlBase64": "[2.0.0, )" - } - } - } - } -} \ No newline at end of file diff --git a/Applications/IdentityDeletionJobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs b/Applications/IdentityDeletionJobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs index 28ae620205..f407f22b97 100644 --- a/Applications/IdentityDeletionJobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs +++ b/Applications/IdentityDeletionJobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs @@ -3,6 +3,7 @@ using Backbone.Modules.Devices.Domain.Entities.Identities; using Backbone.Modules.Devices.Infrastructure.Persistence.Database; using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Domain.Ids; using Backbone.Modules.Messages.Infrastructure.Persistence.Database; using Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates; using Backbone.Modules.Relationships.Infrastructure.Persistence.Database; @@ -158,7 +159,7 @@ private async Task SeedDatabaseWithMessage(IdentityAddress from, Identi { var dbContext = GetService(); - var recipient = new RecipientInformation(to, []); + var recipient = new RecipientInformation(to, RelationshipId.New(), []); var message = new Message(from, DeviceId.New(), [], [], [recipient]); await dbContext.SaveEntity(message); diff --git a/BuildingBlocks/src/BuildingBlocks.SDK/Crypto/CryptoSignaturePublicKey.cs b/BuildingBlocks/src/BuildingBlocks.SDK/Crypto/CryptoSignaturePublicKey.cs index 3c78202422..bb6fd5afa6 100644 --- a/BuildingBlocks/src/BuildingBlocks.SDK/Crypto/CryptoSignaturePublicKey.cs +++ b/BuildingBlocks/src/BuildingBlocks.SDK/Crypto/CryptoSignaturePublicKey.cs @@ -6,6 +6,6 @@ namespace Backbone.BuildingBlocks.SDK.Crypto; public class CryptoSignaturePublicKey { - public required CryptoExchangeAlgorithm alg; - public required string pub; + public required CryptoExchangeAlgorithm alg { get; set; } + public required string pub { get; set; } } diff --git a/Modules/Devices/src/Devices.Application/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEventHandler.cs b/Modules/Devices/src/Devices.Application/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEventHandler.cs index 3e5a1994a3..818c804c32 100644 --- a/Modules/Devices/src/Devices.Application/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEventHandler.cs +++ b/Modules/Devices/src/Devices.Application/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEventHandler.cs @@ -16,6 +16,9 @@ public ExternalEventCreatedDomainEventHandler(IPushNotificationSender pushSender public async Task Handle(ExternalEventCreatedDomainEvent @event) { + if (@event.IsDeliveryBlocked) + return; + await _pushSenderService.SendNotification(@event.Owner, new ExternalEventCreatedPushNotification(), CancellationToken.None); } } diff --git a/Modules/Devices/src/Devices.Domain/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEvent.cs b/Modules/Devices/src/Devices.Domain/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEvent.cs index 0e4f5b623d..27d3b7a3d4 100644 --- a/Modules/Devices/src/Devices.Domain/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEvent.cs +++ b/Modules/Devices/src/Devices.Domain/DomainEvents/Incoming/ExternalEventCreated/ExternalEventCreatedDomainEvent.cs @@ -5,4 +5,5 @@ namespace Backbone.Modules.Devices.Domain.DomainEvents.Incoming.ExternalEventCre public class ExternalEventCreatedDomainEvent : DomainEvent { public required string Owner { get; set; } + public required bool IsDeliveryBlocked { get; set; } } diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/DomainEvents/Incoming/ExternalEventCreatedDomainEventHandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/DomainEvents/Incoming/ExternalEventCreatedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..5640906349 --- /dev/null +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/DomainEvents/Incoming/ExternalEventCreatedDomainEventHandlerTests.cs @@ -0,0 +1,44 @@ +using Backbone.BuildingBlocks.Application.PushNotifications; +using Backbone.Modules.Devices.Application.DomainEvents.Incoming.ExternalEventCreated; +using Backbone.Modules.Devices.Application.Infrastructure.PushNotifications.ExternalEvents; +using Backbone.Modules.Devices.Domain.DomainEvents.Incoming.ExternalEventCreated; +using FakeItEasy; + +namespace Backbone.Modules.Devices.Application.Tests.Tests.DomainEvents.Incoming; + +public class ExternalEventCreatedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public async Task Sends_a_push_notification_to_the_owner_of_the_external_event() + { + // Arrange + var mockPushSender = A.Fake(); + + var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender); + + var externalEventOwner = CreateRandomIdentityAddress(); + + // Act + await handler.Handle(new ExternalEventCreatedDomainEvent { Owner = externalEventOwner, IsDeliveryBlocked = false }); + + // Assert + A.CallTo(() => mockPushSender.SendNotification(externalEventOwner, A._, A._)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task Does_not_send_a_push_notification_when_delivery_of_the_external_event_is_blocked() + { + // Arrange + var mockPushSender = A.Fake(); + + var handler = new ExternalEventCreatedDomainEventHandler(mockPushSender); + + var externalEventOwner = CreateRandomIdentityAddress(); + + // Act + await handler.Handle(new ExternalEventCreatedDomainEvent { Owner = externalEventOwner, IsDeliveryBlocked = true }); + + // Assert + A.CallTo(() => mockPushSender.SendNotification(externalEventOwner, A._, A._)).MustNotHaveHappened(); + } +} diff --git a/Modules/Messages/src/Messages.Application/Messages/Commands/SendMessage/Handler.cs b/Modules/Messages/src/Messages.Application/Messages/Commands/SendMessage/Handler.cs index 8e048285f5..7cc426b7b6 100644 --- a/Modules/Messages/src/Messages.Application/Messages/Commands/SendMessage/Handler.cs +++ b/Modules/Messages/src/Messages.Application/Messages/Commands/SendMessage/Handler.cs @@ -72,7 +72,7 @@ private async Task> ValidateRecipients(SendMessageCom numberOfUnreceivedMessagesFromActiveIdentity, _options.MaxNumberOfUnreceivedMessagesFromOneSender); - var recipient = new RecipientInformation(recipientDto.Address, recipientDto.EncryptedKey); + var recipient = new RecipientInformation(recipientDto.Address, relationshipBetweenSenderAndRecipient.Id, recipientDto.EncryptedKey); recipients.Add(recipient); diff --git a/Modules/Messages/src/Messages.Domain/DomainErrors.cs b/Modules/Messages/src/Messages.Domain/DomainErrors.cs index d92951e5d7..19470bf0e6 100644 --- a/Modules/Messages/src/Messages.Domain/DomainErrors.cs +++ b/Modules/Messages/src/Messages.Domain/DomainErrors.cs @@ -10,7 +10,7 @@ public static DomainError RelationshipToRecipientNotActive(string recipient = "" return new DomainError( "error.platform.validation.message.relationshipToRecipientNotActive", - $"Cannot send message to {recipientText} because the relationship to it is not active. If it's terminated, you'll need to reactivate it to be able to send messages again."); + $"Cannot send message to {recipientText} because the relationship to it is not active."); } public static DomainError MaxNumberOfUnreceivedMessagesReached(string recipient = "") diff --git a/Modules/Messages/src/Messages.Domain/DomainEvents/Outgoing/MessageCreatedDomainEvent.cs b/Modules/Messages/src/Messages.Domain/DomainEvents/Outgoing/MessageCreatedDomainEvent.cs index 7b52bb99c4..ea15d08518 100644 --- a/Modules/Messages/src/Messages.Domain/DomainEvents/Outgoing/MessageCreatedDomainEvent.cs +++ b/Modules/Messages/src/Messages.Domain/DomainEvents/Outgoing/MessageCreatedDomainEvent.cs @@ -8,11 +8,21 @@ public class MessageCreatedDomainEvent : DomainEvent public MessageCreatedDomainEvent(Message message) : base($"{message.Id}/Created") { Id = message.Id.Value; - Recipients = message.Recipients.Select(r => r.Address.ToString()); + Recipients = message.Recipients.Select(r => new Recipient + { + Address = r.Address.Value, + RelationshipId = r.RelationshipId.Value + }); CreatedBy = message.CreatedBy.Value; } public string Id { get; } - public IEnumerable Recipients { get; } + public IEnumerable Recipients { get; } public string CreatedBy { get; } + + public class Recipient + { + public required string Address { get; set; } + public required string RelationshipId { get; set; } + } } diff --git a/Modules/Messages/src/Messages.Domain/Entities/RecipientInformation.cs b/Modules/Messages/src/Messages.Domain/Entities/RecipientInformation.cs index bf83fa8992..34060e2bcf 100644 --- a/Modules/Messages/src/Messages.Domain/Entities/RecipientInformation.cs +++ b/Modules/Messages/src/Messages.Domain/Entities/RecipientInformation.cs @@ -12,18 +12,21 @@ private RecipientInformation() { // This constructor is for EF Core only; initializing the properties with null is therefore not a problem Address = null!; + RelationshipId = null!; EncryptedKey = null!; MessageId = null!; } - public RecipientInformation(IdentityAddress address, byte[] encryptedKey) + public RecipientInformation(IdentityAddress address, RelationshipId relationshipId, byte[] encryptedKey) { Address = address; + RelationshipId = relationshipId; EncryptedKey = encryptedKey; MessageId = null!; // we just assign null to satisfy the compiler; it will be set by EF Core } public int Id { get; } + public RelationshipId RelationshipId { get; } public IdentityAddress Address { get; private set; } public byte[] EncryptedKey { get; } public DateTime? ReceivedAt { get; private set; } diff --git a/Modules/Messages/src/Messages.Domain/Entities/Relationship.cs b/Modules/Messages/src/Messages.Domain/Entities/Relationship.cs index 237762ae0e..c307c7d92a 100644 --- a/Modules/Messages/src/Messages.Domain/Entities/Relationship.cs +++ b/Modules/Messages/src/Messages.Domain/Entities/Relationship.cs @@ -37,7 +37,7 @@ private Relationship(RelationshipId id, IdentityAddress from, IdentityAddress to public void EnsureSendingMessagesIsAllowed(IdentityAddress activeIdentity, int numberOfUnreceivedMessagesFromActiveIdentity, int maxNumberOfUnreceivedMessagesFromOneSender) { - if (Status != RelationshipStatus.Active) + if (Status is not (RelationshipStatus.Active or RelationshipStatus.Terminated)) throw new DomainException(DomainErrors.RelationshipToRecipientNotActive(GetPeerOf(activeIdentity))); if (numberOfUnreceivedMessagesFromActiveIdentity >= maxNumberOfUnreceivedMessagesFromOneSender) diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.Designer.cs b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.Designer.cs new file mode 100644 index 0000000000..dc50bc15fd --- /dev/null +++ b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.Designer.cs @@ -0,0 +1,209 @@ +// +using System; +using Backbone.Modules.Messages.Infrastructure.Persistence.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Backbone.Modules.Messages.Infrastructure.Database.Postgres.Migrations +{ + [DbContext(typeof(MessagesDbContext))] + [Migration("20241015104418_AddRelationshipIdToRecipientInformation")] + partial class AddRelationshipIdToRecipientInformation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Messages") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Attachment", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("MessageId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id", "MessageId"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Message", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Body") + .HasColumnType("bytea"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("CreatedBy"), "hash"); + + b.ToTable("Messages", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.RecipientInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("EncryptedKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("IsRelationshipDecomposedByRecipient") + .HasColumnType("boolean"); + + b.Property("IsRelationshipDecomposedBySender") + .HasColumnType("boolean"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("ReceivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReceivedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("RelationshipId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("ReceivedAt"); + + b.HasIndex("Address", "MessageId"); + + b.ToTable("RecipientInformation", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("From") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("To") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Attachment", b => + { + b.HasOne("Backbone.Modules.Messages.Domain.Entities.Message", null) + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.RecipientInformation", b => + { + b.HasOne("Backbone.Modules.Messages.Domain.Entities.Message", null) + .WithMany("Recipients") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Message", b => + { + b.Navigation("Attachments"); + + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.cs b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.cs new file mode 100644 index 0000000000..4315487def --- /dev/null +++ b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/20241015104418_AddRelationshipIdToRecipientInformation.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Messages.Infrastructure.Database.Postgres.Migrations +{ + /// + public partial class AddRelationshipIdToRecipientInformation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RelationshipId", + schema: "Messages", + table: "RecipientInformation", + type: "character(20)", + unicode: false, + fixedLength: true, + maxLength: 20, + nullable: false, + defaultValue: ""); + + // Fill the new RelationshipId column with the existing relationships between sender and recipient + migrationBuilder.Sql(""" + UPDATE "Messages"."RecipientInformation" AS ri + SET "RelationshipId" = COALESCE(( + SELECT "Id" + FROM "Relationships"."Relationships" AS r + WHERE (r."From" = m."CreatedBy" AND r."To" = ri."Address") OR (r."From" = ri."Address" AND r."To" = m."CreatedBy") + ORDER BY r."CreatedAt" DESC + LIMIT 1 + ), 'REL00000000000000000') + FROM "Messages"."Messages" AS m + WHERE m."Id" = ri."MessageId" AND ri."RelationshipId" = ''; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RelationshipId", + schema: "Messages", + table: "RecipientInformation"); + } + } +} diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/MessagesDbContextModelSnapshot.cs b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/MessagesDbContextModelSnapshot.cs index 348e5ab371..4fb52ccf22 100644 --- a/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/MessagesDbContextModelSnapshot.cs +++ b/Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Migrations/MessagesDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Messages") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -122,6 +122,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character(20)") .IsFixedLength(); + b.Property("RelationshipId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + b.HasKey("Id"); b.HasIndex("MessageId"); diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.Designer.cs b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.Designer.cs new file mode 100644 index 0000000000..dd044712c8 --- /dev/null +++ b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.Designer.cs @@ -0,0 +1,208 @@ +// +using System; +using Backbone.Modules.Messages.Infrastructure.Persistence.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Backbone.Modules.Messages.Infrastructure.Database.SqlServer.Migrations +{ + [DbContext(typeof(MessagesDbContext))] + [Migration("20241015104416_AddRelationshipIdToRecipientInformation")] + partial class AddRelationshipIdToRecipientInformation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Messages") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Attachment", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("MessageId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id", "MessageId"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Message", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Body") + .HasColumnType("varbinary(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy") + .HasAnnotation("Npgsql:IndexMethod", "hash"); + + b.ToTable("Messages", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.RecipientInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("EncryptedKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("IsRelationshipDecomposedByRecipient") + .HasColumnType("bit"); + + b.Property("IsRelationshipDecomposedBySender") + .HasColumnType("bit"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("ReceivedAt") + .HasColumnType("datetime2"); + + b.Property("ReceivedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("RelationshipId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("ReceivedAt"); + + b.HasIndex("Address", "MessageId"); + + b.ToTable("RecipientInformation", "Messages"); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("From") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("To") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Attachment", b => + { + b.HasOne("Backbone.Modules.Messages.Domain.Entities.Message", null) + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.RecipientInformation", b => + { + b.HasOne("Backbone.Modules.Messages.Domain.Entities.Message", null) + .WithMany("Recipients") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Messages.Domain.Entities.Message", b => + { + b.Navigation("Attachments"); + + b.Navigation("Recipients"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.cs b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.cs new file mode 100644 index 0000000000..74ca49807b --- /dev/null +++ b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/20241015104416_AddRelationshipIdToRecipientInformation.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Messages.Infrastructure.Database.SqlServer.Migrations +{ + /// + public partial class AddRelationshipIdToRecipientInformation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RelationshipId", + schema: "Messages", + table: "RecipientInformation", + type: "char(20)", + unicode: false, + fixedLength: true, + maxLength: 20, + nullable: false, + defaultValue: ""); + + // Fill the new RelationshipId column with the existing relationships between sender and recipient + migrationBuilder.Sql(""" + UPDATE ri + SET ri.RelationshipId = COALESCE(( + SELECT TOP 1 r.Id + FROM Relationships.Relationships AS r + WHERE (r.[From] = m.CreatedBy AND r.[To] = ri.Address) OR (r.[From] = ri.Address AND r.[To] = m.CreatedBy) + ORDER BY r.CreatedAt DESC + ), 'REL00000000000000000') + FROM Messages.RecipientInformation AS ri + JOIN Messages.Messages AS m ON m.Id = ri.MessageId + WHERE ri.RelationshipId = ''; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RelationshipId", + schema: "Messages", + table: "RecipientInformation"); + } + } +} diff --git a/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/MessagesDbContextModelSnapshot.cs b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/MessagesDbContextModelSnapshot.cs index 89619814b3..d8d35afd74 100644 --- a/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/MessagesDbContextModelSnapshot.cs +++ b/Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Migrations/MessagesDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Messages") - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -74,7 +74,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("CreatedBy"); + b.HasIndex("CreatedBy") + .HasAnnotation("Npgsql:IndexMethod", "hash"); b.ToTable("Messages", "Messages"); }); @@ -120,6 +121,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("char(20)") .IsFixedLength(); + b.Property("RelationshipId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + b.HasKey("Id"); b.HasIndex("MessageId"); diff --git a/Modules/Messages/src/Messages.Infrastructure/Persistence/Database/EntityConfigurations/RecipientInformationEntityTypeConfiguration.cs b/Modules/Messages/src/Messages.Infrastructure/Persistence/Database/EntityConfigurations/RecipientInformationEntityTypeConfiguration.cs index e66296c950..8fff400a74 100644 --- a/Modules/Messages/src/Messages.Infrastructure/Persistence/Database/EntityConfigurations/RecipientInformationEntityTypeConfiguration.cs +++ b/Modules/Messages/src/Messages.Infrastructure/Persistence/Database/EntityConfigurations/RecipientInformationEntityTypeConfiguration.cs @@ -21,5 +21,6 @@ public override void Configure(EntityTypeBuilder builder) builder.Property(r => r.IsRelationshipDecomposedByRecipient); builder.Property(r => r.IsRelationshipDecomposedBySender); + builder.Property(r => r.RelationshipId); } } diff --git a/Modules/Messages/test/Messages.Application.Tests/TestHelpers/TestData.cs b/Modules/Messages/test/Messages.Application.Tests/TestHelpers/TestData.cs index 3778e9c49e..589a9c26a4 100644 --- a/Modules/Messages/test/Messages.Application.Tests/TestHelpers/TestData.cs +++ b/Modules/Messages/test/Messages.Application.Tests/TestHelpers/TestData.cs @@ -1,5 +1,6 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Domain.Ids; namespace Backbone.Modules.Messages.Application.Tests.TestHelpers; @@ -10,7 +11,7 @@ public static Message CreateMessageWithOneRecipient(IdentityAddress? senderAddre senderAddress ??= CreateRandomIdentityAddress(); recipientAddress ??= CreateRandomIdentityAddress(); - var recipient = new RecipientInformation(recipientAddress, []); + var recipient = new RecipientInformation(recipientAddress, RelationshipId.New(), []); return new Message(senderAddress, CreateRandomDeviceId(), [], [], [recipient]); } @@ -20,6 +21,7 @@ public static Message CreateMessageWithTwoRecipients(IdentityAddress? senderAddr recipient1Address ??= CreateRandomIdentityAddress(); recipient2Address ??= CreateRandomIdentityAddress(); - return new Message(senderAddress, CreateRandomDeviceId(), [], [], [new RecipientInformation(recipient1Address, []), new RecipientInformation(recipient2Address, [])]); + return new Message(senderAddress, CreateRandomDeviceId(), [], [], + [new RecipientInformation(recipient1Address, RelationshipId.New(), []), new RecipientInformation(recipient2Address, RelationshipId.New(), [])]); } } diff --git a/Modules/Messages/test/Messages.Domain.Tests/Messages/AnonymizeParticipantTests.cs b/Modules/Messages/test/Messages.Domain.Tests/Messages/AnonymizeParticipantTests.cs index ffb4408d48..34661dbadd 100644 --- a/Modules/Messages/test/Messages.Domain.Tests/Messages/AnonymizeParticipantTests.cs +++ b/Modules/Messages/test/Messages.Domain.Tests/Messages/AnonymizeParticipantTests.cs @@ -1,6 +1,7 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Messages.Domain.DomainEvents.Outgoing; using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Domain.Ids; namespace Backbone.Modules.Messages.Domain.Tests.Messages; @@ -116,7 +117,7 @@ public void Raises_a_MessageOrphanedDomainEvent_when_sender_and_all_recipients_a private static Message CreateMessage((IdentityAddress createdBy, IEnumerable recipients) parameters) { var recipientInformation = parameters.recipients.Select(recipientIdentityAddress => - new RecipientInformation(recipientIdentityAddress, []) + new RecipientInformation(recipientIdentityAddress, RelationshipId.New(), []) ).ToList(); var message = new Message( diff --git a/Modules/Messages/test/Messages.Domain.Tests/Messages/CreationTests.cs b/Modules/Messages/test/Messages.Domain.Tests/Messages/CreationTests.cs index ecb32d018c..c6cd89c086 100644 --- a/Modules/Messages/test/Messages.Domain.Tests/Messages/CreationTests.cs +++ b/Modules/Messages/test/Messages.Domain.Tests/Messages/CreationTests.cs @@ -1,5 +1,6 @@ using Backbone.Modules.Messages.Domain.DomainEvents.Outgoing; using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Domain.Ids; namespace Backbone.Modules.Messages.Domain.Tests.Messages; @@ -10,7 +11,8 @@ public void Raises_MessageCreatedDomainEvent_when_created() { // Arrange var sender = CreateRandomIdentityAddress(); - var recipient = new RecipientInformation(CreateRandomIdentityAddress(), []); + var relationshipId = RelationshipId.New(); + var recipient = new RecipientInformation(CreateRandomIdentityAddress(), relationshipId, []); // Act var message = new Message( @@ -26,6 +28,7 @@ public void Raises_MessageCreatedDomainEvent_when_created() domainEvent.CreatedBy.Should().Be(sender); domainEvent.Id.Should().Be(message.Id); domainEvent.Recipients.Should().HaveCount(1); - domainEvent.Recipients.First().Should().Be(recipient.Address); + domainEvent.Recipients.First().Address.Should().Be(recipient.Address); + domainEvent.Recipients.First().RelationshipId.Should().Be(relationshipId); } } diff --git a/Modules/Messages/test/Messages.Domain.Tests/Relationships/RelationshipTests.cs b/Modules/Messages/test/Messages.Domain.Tests/Relationships/EnsureSendingMessagesIsAllowedTests.cs similarity index 83% rename from Modules/Messages/test/Messages.Domain.Tests/Relationships/RelationshipTests.cs rename to Modules/Messages/test/Messages.Domain.Tests/Relationships/EnsureSendingMessagesIsAllowedTests.cs index 248cf476ed..c9342aad92 100644 --- a/Modules/Messages/test/Messages.Domain.Tests/Relationships/RelationshipTests.cs +++ b/Modules/Messages/test/Messages.Domain.Tests/Relationships/EnsureSendingMessagesIsAllowedTests.cs @@ -5,10 +5,10 @@ namespace Backbone.Modules.Messages.Domain.Tests.Relationships; -public class RelationshipTests : AbstractTestsBase +public class EnsureSendingMessagesIsAllowedTests : AbstractTestsBase { [Fact] - public void Relationship_must_be_active_to_allow_sending_messages() + public void Throws_if_relationship_is_pending() { // Arrange var relationship = CreateRelationship(RelationshipStatus.Pending); @@ -21,7 +21,7 @@ public void Relationship_must_be_active_to_allow_sending_messages() } [Fact] - public void Max_number_of_unreceived_messages_must_not_be_reached() + public void Throws_if_max_number_of_unreceived_messages_is_reached() { // Arrange var relationship = CreateRelationship(); @@ -34,7 +34,7 @@ public void Max_number_of_unreceived_messages_must_not_be_reached() } [Fact] - public void Relationship_cannot_be_terminated_to_allow_sending_messages() + public void Does_not_throw_if_relationship_is_terminated() { // Arrange var relationship = CreateRelationship(RelationshipStatus.Terminated); @@ -43,7 +43,7 @@ public void Relationship_cannot_be_terminated_to_allow_sending_messages() var acting = () => relationship.EnsureSendingMessagesIsAllowed(CreateRandomIdentityAddress(), 0, 5); // Assert - acting.Should().Throw().Which.Code.Should().Be("error.platform.validation.message.relationshipToRecipientNotActive"); + acting.Should().NotThrow(); } #region helpers diff --git a/Modules/Messages/test/Messages.Domain.Tests/TestHelpers/TestData.cs b/Modules/Messages/test/Messages.Domain.Tests/TestHelpers/TestData.cs index f6648e5010..a36da5f2bb 100644 --- a/Modules/Messages/test/Messages.Domain.Tests/TestHelpers/TestData.cs +++ b/Modules/Messages/test/Messages.Domain.Tests/TestHelpers/TestData.cs @@ -1,5 +1,6 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Domain.Ids; namespace Backbone.Modules.Messages.Domain.Tests.TestHelpers; @@ -10,7 +11,7 @@ public static Message CreateMessageWithOneRecipient(IdentityAddress? senderAddre senderAddress ??= CreateRandomIdentityAddress(); recipientAddress ??= CreateRandomIdentityAddress(); - var recipient = new RecipientInformation(recipientAddress, []); + var recipient = new RecipientInformation(recipientAddress, RelationshipId.New(), []); return new Message(senderAddress, CreateRandomDeviceId(), [], [], [recipient]); } @@ -20,6 +21,7 @@ public static Message CreateMessageWithTwoRecipients(IdentityAddress? senderAddr recipient1Address ??= CreateRandomIdentityAddress(); recipient2Address ??= CreateRandomIdentityAddress(); - return new Message(senderAddress, CreateRandomDeviceId(), [], [], [new RecipientInformation(recipient1Address, []), new RecipientInformation(recipient2Address, [])]); + return new Message(senderAddress, CreateRandomDeviceId(), [], [], + [new RecipientInformation(recipient1Address, RelationshipId.New(), []), new RecipientInformation(recipient2Address, RelationshipId.New(), [])]); } } diff --git a/Modules/Quotas/src/Quotas.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs b/Modules/Quotas/src/Quotas.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs index 641f831db9..e85666c0f1 100644 --- a/Modules/Quotas/src/Quotas.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs +++ b/Modules/Quotas/src/Quotas.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs @@ -4,7 +4,5 @@ namespace Backbone.Modules.Quotas.Domain.DomainEvents.Incoming.MessageCreated; public class MessageCreatedDomainEvent : DomainEvent { - public required string Id { get; set; } - public required IEnumerable Recipients { get; set; } public required string CreatedBy { get; set; } } diff --git a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/RelationshipReactivationCompletedDomainEvent.cs b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/RelationshipReactivationCompletedDomainEvent.cs index b3fc16b569..1a15c5de3c 100644 --- a/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/RelationshipReactivationCompletedDomainEvent.cs +++ b/Modules/Relationships/src/Relationships.Domain/DomainEvents/Outgoing/RelationshipReactivationCompletedDomainEvent.cs @@ -3,6 +3,7 @@ using Backbone.Modules.Relationships.Domain.Aggregates.Relationships; namespace Backbone.Modules.Relationships.Domain.DomainEvents.Outgoing; + public class RelationshipReactivationCompletedDomainEvent : DomainEvent { public RelationshipReactivationCompletedDomainEvent(Relationship relationship, IdentityAddress peer) @@ -10,8 +11,10 @@ public RelationshipReactivationCompletedDomainEvent(Relationship relationship, I { RelationshipId = relationship.Id; Peer = peer; + NewRelationshipStatus = relationship.Status.ToString(); } public string RelationshipId { get; } + public string NewRelationshipStatus { get; set; } public string Peer { get; } } diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEventHandler.cs index 906885cea3..3d2a4a3e12 100644 --- a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEventHandler.cs +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEventHandler.cs @@ -2,6 +2,7 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; using Backbone.Modules.Synchronization.Application.Infrastructure; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.MessageCreated; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; using Backbone.Modules.Synchronization.Domain.Entities.Sync; using Microsoft.Extensions.Logging; @@ -10,11 +11,13 @@ namespace Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.Mes public class MessageCreatedDomainEventHandler : IDomainEventHandler { private readonly ISynchronizationDbContext _dbContext; + private readonly IRelationshipsRepository _relationshipsRepository; private readonly ILogger _logger; - public MessageCreatedDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) + public MessageCreatedDomainEventHandler(ISynchronizationDbContext dbContext, IRelationshipsRepository relationshipsRepository, ILogger logger) { _dbContext = dbContext; + _relationshipsRepository = relationshipsRepository; _logger = logger; } @@ -33,13 +36,36 @@ public async Task Handle(MessageCreatedDomainEvent @event) private async Task CreateMessageReceivedExternalEvents(MessageCreatedDomainEvent @event) { + var relationships = await _relationshipsRepository.GetRelationships(@event.Recipients.Select(r => RelationshipId.Parse(r.RelationshipId)), CancellationToken.None); + foreach (var recipient in @event.Recipients) { - var payload = new MessageReceivedExternalEvent.EventPayload { Id = @event.Id }; + var relationship = GetRelationshipBetween(@event.CreatedBy, recipient.Address, relationships); + await CreateExternalEventForRecipient(@event, recipient.Address, relationship); + } + } - var externalEvent = new MessageReceivedExternalEvent(IdentityAddress.Parse(recipient), payload); + private async Task CreateExternalEventForRecipient(MessageCreatedDomainEvent @event, string recipient, Relationship relationship) + { + var payload = new MessageReceivedExternalEvent.EventPayload { Id = @event.Id }; - await _dbContext.CreateExternalEvent(externalEvent); - } + var externalEvent = new MessageReceivedExternalEvent(IdentityAddress.Parse(recipient), payload, relationship.Id); + + if (relationship.Status == RelationshipStatus.Terminated) + externalEvent.BlockDelivery(); + + await _dbContext.CreateExternalEvent(externalEvent); + } + + private static Relationship GetRelationshipBetween(string identity1, string identity2, List relationships) + { + var relationship = relationships.Single(r => + { + var createdByAddress = IdentityAddress.ParseUnsafe(identity1); + var recipientAddress = IdentityAddress.ParseUnsafe(identity2); + + return r.IsBetween(createdByAddress, recipientAddress); + }); + return relationship; } } diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEventHandler.cs index 2dfc911cf0..b262b971d8 100644 --- a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEventHandler.cs +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEventHandler.cs @@ -22,6 +22,7 @@ public async Task Handle(RelationshipReactivationCompletedDomainEvent @event) try { await CreateRelationshipReactivationCompletedExternalEvent(@event); + await UnblockMessageReceivedExternalEvents(@event); } catch (Exception ex) { @@ -38,4 +39,20 @@ private async Task CreateRelationshipReactivationCompletedExternalEvent(Relation await _dbContext.CreateExternalEvent(externalEvent); } + + private async Task UnblockMessageReceivedExternalEvents(RelationshipReactivationCompletedDomainEvent @event) + { + if (@event.NewRelationshipStatus != "Active") + return; + + var externalEvents = await _dbContext.GetBlockedExternalEventsWithTypeAndContext(ExternalEventType.MessageReceived, @event.RelationshipId, CancellationToken.None); + + foreach (var externalEvent in externalEvents) + { + externalEvent.UnblockDelivery(); + _dbContext.Set().Update(externalEvent); + } + + await _dbContext.SaveChangesAsync(CancellationToken.None); + } } diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationRequested/RelationshipReactivationRequestedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationRequested/RelationshipReactivationRequestedDomainEventHandler.cs index 3392692f5b..c420b390c6 100644 --- a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationRequested/RelationshipReactivationRequestedDomainEventHandler.cs +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipReactivationRequested/RelationshipReactivationRequestedDomainEventHandler.cs @@ -1,5 +1,4 @@ using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; -using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.MessageCreated; using Backbone.Modules.Synchronization.Application.Infrastructure; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.RelationshipReactivationRequested; using Backbone.Modules.Synchronization.Domain.Entities.Sync; @@ -10,9 +9,9 @@ namespace Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.Rel public class RelationshipReactivationRequestedDomainEventHandler : IDomainEventHandler { private readonly ISynchronizationDbContext _dbContext; - private readonly ILogger _logger; + private readonly ILogger _logger; - public RelationshipReactivationRequestedDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) + public RelationshipReactivationRequestedDomainEventHandler(ISynchronizationDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; diff --git a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEventHandler.cs b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEventHandler.cs index 257c7e058a..a244a4a870 100644 --- a/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEventHandler.cs +++ b/Modules/Synchronization/src/Synchronization.Application/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEventHandler.cs @@ -22,6 +22,7 @@ public async Task Handle(RelationshipStatusChangedDomainEvent @event) try { await CreateRelationshipStatusChangedExternalEvent(@event); + await DeleteExternalEvents(@event); } catch (Exception ex) { @@ -42,4 +43,14 @@ private async Task CreateRelationshipStatusChangedExternalEvent(RelationshipStat await _dbContext.CreateExternalEvent(externalEvent); } + + private async Task DeleteExternalEvents(RelationshipStatusChangedDomainEvent @event) + { + // when a relationship was decomposed, we can delete all external events related to it for + // the identity that initiated the decomposition + if (@event.NewStatus is "DeletionProposed" or "ReadyForDeletion") + { + await _dbContext.DeleteUnsyncedExternalEventsWithOwnerAndContext(@event.Initiator, @event.RelationshipId); + } + } } diff --git a/Modules/Synchronization/src/Synchronization.Application/Extensions/ExternalEventsQueryableExtensions.cs b/Modules/Synchronization/src/Synchronization.Application/Extensions/ExternalEventsQueryableExtensions.cs index 9f4a6ab697..e1aa928ba2 100644 --- a/Modules/Synchronization/src/Synchronization.Application/Extensions/ExternalEventsQueryableExtensions.cs +++ b/Modules/Synchronization/src/Synchronization.Application/Extensions/ExternalEventsQueryableExtensions.cs @@ -20,6 +20,26 @@ public static IQueryable Unsynced(this IQueryable return query.Where(e => e.SyncRunId == null); } + public static IQueryable NotBlocked(this IQueryable query) + { + return query.Where(e => !e.IsDeliveryBlocked); + } + + public static IQueryable Blocked(this IQueryable query) + { + return query.Where(e => e.IsDeliveryBlocked); + } + + public static IQueryable WithType(this IQueryable query, ExternalEventType type) + { + return query.Where(e => e.Type == type); + } + + public static IQueryable WithContext(this IQueryable query, string context) + { + return query.Where(e => e.Context == context); + } + public static IQueryable AssignedToSyncRun(this IQueryable query, SyncRunId id) { return query.Where(i => i.SyncRunId == id); diff --git a/Modules/Synchronization/src/Synchronization.Application/Infrastructure/IRelationshipsRepository.cs b/Modules/Synchronization/src/Synchronization.Application/Infrastructure/IRelationshipsRepository.cs new file mode 100644 index 0000000000..975e0c05b0 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Application/Infrastructure/IRelationshipsRepository.cs @@ -0,0 +1,8 @@ +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; + +namespace Backbone.Modules.Synchronization.Application.Infrastructure; + +public interface IRelationshipsRepository +{ + Task> GetRelationships(IEnumerable ids, CancellationToken cancellationToken); +} diff --git a/Modules/Synchronization/src/Synchronization.Application/Infrastructure/ISynchronizationDbContext.cs b/Modules/Synchronization/src/Synchronization.Application/Infrastructure/ISynchronizationDbContext.cs index be1c694754..4e75909568 100644 --- a/Modules/Synchronization/src/Synchronization.Application/Infrastructure/ISynchronizationDbContext.cs +++ b/Modules/Synchronization/src/Synchronization.Application/Infrastructure/ISynchronizationDbContext.cs @@ -14,12 +14,14 @@ Task> GetDatawalletModifications(Iden Task GetDatawalletForInsertion(IdentityAddress owner, CancellationToken cancellationToken); Task GetDatawallet(IdentityAddress owner, CancellationToken cancellationToken); Task CreateExternalEvent(ExternalEvent externalEvent); + Task DeleteUnsyncedExternalEventsWithOwnerAndContext(IdentityAddress owner, string context); Task GetSyncRun(SyncRunId syncRunId, IdentityAddress createdBy, CancellationToken cancellationToken); Task IsActiveSyncRunAvailable(IdentityAddress createdBy, CancellationToken cancellationToken); Task GetSyncRunAsNoTracking(SyncRunId syncRunId, IdentityAddress createdBy, CancellationToken cancellationToken); Task GetSyncRunWithExternalEvents(SyncRunId syncRunId, IdentityAddress createdBy, CancellationToken cancellationToken); Task GetPreviousSyncRunWithExternalEvents(IdentityAddress createdBy, CancellationToken cancellationToken); Task> GetUnsyncedExternalEvents(IdentityAddress owner, byte maxErrorCount, CancellationToken cancellationToken); + Task> GetBlockedExternalEventsWithTypeAndContext(ExternalEventType type, string context, CancellationToken cancellationToken); Task> GetExternalEventsOfSyncRun(PaginationFilter paginationFilter, IdentityAddress owner, SyncRunId syncRunId, CancellationToken cancellationToken); diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs index 1b69edba43..c9581fa8b8 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/MessageCreated/MessageCreatedDomainEvent.cs @@ -5,5 +5,12 @@ namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.MessageC public class MessageCreatedDomainEvent : DomainEvent { public required string Id { get; set; } - public required IEnumerable Recipients { get; set; } + public required string CreatedBy { get; set; } + public required IEnumerable Recipients { get; set; } + + public class Recipient + { + public required string Address { get; set; } + public required string RelationshipId { get; set; } + } } diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEvent.cs index 471d10f202..808a814f89 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipReactivationCompleted/RelationshipReactivationCompletedDomainEvent.cs @@ -1,14 +1,10 @@ using Backbone.BuildingBlocks.Domain.Events; namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.RelationshipReactivationCompleted; + public class RelationshipReactivationCompletedDomainEvent : DomainEvent { - public RelationshipReactivationCompletedDomainEvent(string relationshipId, string peer) - { - RelationshipId = relationshipId; - Peer = peer; - } - - public string RelationshipId { get; } - public string Peer { get; } + public required string RelationshipId { get; set; } + public required string NewRelationshipStatus { get; set; } + public required string Peer { get; set; } } diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEvent.cs index d902cf4ceb..7a30ba0ef0 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Incoming/RelationshipStatusChanged/RelationshipStatusChangedDomainEvent.cs @@ -5,6 +5,7 @@ namespace Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.Relation public class RelationshipStatusChangedDomainEvent : DomainEvent { public required string RelationshipId { get; set; } + public required string Initiator { get; set; } public required string Peer { get; set; } public required string NewStatus { get; set; } } diff --git a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Outgoing/ExternalEventCreatedDomainEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Outgoing/ExternalEventCreatedDomainEvent.cs index a3bd583141..a690f2af21 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Outgoing/ExternalEventCreatedDomainEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/DomainEvents/Outgoing/ExternalEventCreatedDomainEvent.cs @@ -9,8 +9,10 @@ public ExternalEventCreatedDomainEvent(ExternalEvent externalEvent) : base($"{ex { EventId = externalEvent.Id; Owner = externalEvent.Owner; + IsDeliveryBlocked = externalEvent.IsDeliveryBlocked; } public string EventId { get; } public string Owner { get; } + public bool IsDeliveryBlocked { get; } } diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/Relationship.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/Relationship.cs new file mode 100644 index 0000000000..90a990d38d --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/Relationship.cs @@ -0,0 +1,25 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; + +namespace Backbone.Modules.Synchronization.Domain.Entities.Relationships; + +public class Relationship +{ + public Relationship(RelationshipId id, IdentityAddress from, IdentityAddress to, RelationshipStatus status) + { + Id = id; + From = from; + To = to; + Status = status; + } + + public RelationshipId Id { get; } = null!; + public IdentityAddress From { get; } = null!; + public IdentityAddress To { get; } = null!; + public RelationshipStatus Status { get; } + + public bool IsBetween(IdentityAddress identity1, IdentityAddress identity2) + { + return From == identity1 && To == identity2 || + From == identity2 && To == identity1; + } +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipId.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipId.cs new file mode 100644 index 0000000000..f4b3508971 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipId.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; +using System.Globalization; +using Backbone.BuildingBlocks.Domain; +using Backbone.BuildingBlocks.Domain.StronglyTypedIds.Records; + +namespace Backbone.Modules.Synchronization.Domain.Entities.Relationships; + +public record RelationshipId(string Value) : StronglyTypedId(Value) +{ + public const int MAX_LENGTH = DEFAULT_MAX_LENGTH; + private const string PREFIX = "REL"; + private static readonly StronglyTypedIdHelpers UTILS = new(PREFIX, DEFAULT_VALID_CHARS, MAX_LENGTH); + + public static RelationshipId Parse(string stringValue) + { + if (!IsValid(stringValue)) + throw new InvalidIdException($"'{stringValue}' is not a valid {nameof(RelationshipId)}."); + + return new RelationshipId(stringValue); + } + + public static bool IsValid(string stringValue) + { + return UTILS.IsValid(stringValue); + } + + public static RelationshipId New() + { + var challengeIdAsString = StringUtils.Generate(DEFAULT_VALID_CHARS, DEFAULT_MAX_LENGTH_WITHOUT_PREFIX); + return new RelationshipId(PREFIX + challengeIdAsString); + } + + public class RelationshipIdTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + var stringValue = value as string; + + return !string.IsNullOrEmpty(stringValue) ? Parse(stringValue) : base.ConvertFrom(context, culture, value); + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipStatus.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipStatus.cs new file mode 100644 index 0000000000..8b9b46b63b --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Relationships/RelationshipStatus.cs @@ -0,0 +1,12 @@ +namespace Backbone.Modules.Synchronization.Domain.Entities.Relationships; + +public enum RelationshipStatus +{ + Pending = 10, + Active = 20, + Rejected = 30, + Revoked = 40, + Terminated = 50, + DeletionProposed = 60, + ReadyForDeletion = 70 +} diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs index 6dc9836e1a..eaa8096085 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/ExternalEvent.cs @@ -12,19 +12,21 @@ public class ExternalEvent : Entity // ReSharper disable once UnusedMember.Local protected ExternalEvent() { + Context = null!; // This constructor is for EF Core only; initializing the properties with null is therefore not a problem Id = null!; Owner = null!; Payload = null!; } - protected ExternalEvent(ExternalEventType type, IdentityAddress owner, object payload) + protected ExternalEvent(ExternalEventType type, IdentityAddress owner, object payload, string? context = null) { Id = ExternalEventId.New(); Type = type; Owner = owner; CreatedAt = SystemTime.UtcNow; Payload = payload; + Context = context; RaiseDomainEvent(new ExternalEventCreatedDomainEvent(this)); } @@ -43,11 +45,24 @@ protected ExternalEvent(ExternalEventType type, IdentityAddress owner, object pa public SyncRunId? SyncRunId { get; private set; } public IReadOnlyCollection Errors => _errors; + public string? Context { get; } + public bool IsDeliveryBlocked { get; private set; } + public void UpdateIndex(long newIndex) { Index = newIndex; } + public void BlockDelivery() + { + IsDeliveryBlocked = true; + } + + public void UnblockDelivery() + { + IsDeliveryBlocked = false; + } + public void AssignToSyncRun(SyncRun syncRun) { SyncRun = syncRun; diff --git a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/MessageReceivedExternalEvent.cs b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/MessageReceivedExternalEvent.cs index 9fbb505faa..320b8d3835 100644 --- a/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/MessageReceivedExternalEvent.cs +++ b/Modules/Synchronization/src/Synchronization.Domain/Entities/Sync/MessageReceivedExternalEvent.cs @@ -1,4 +1,5 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; namespace Backbone.Modules.Synchronization.Domain.Entities.Sync; @@ -10,8 +11,8 @@ private MessageReceivedExternalEvent() // This constructor is for EF Core only; initializing the properties with null is therefore not a problem } - public MessageReceivedExternalEvent(IdentityAddress owner, EventPayload payload) - : base(ExternalEventType.MessageReceived, owner, payload) + public MessageReceivedExternalEvent(IdentityAddress owner, EventPayload payload, RelationshipId relationshipId) + : base(ExternalEventType.MessageReceived, owner, payload, relationshipId.Value) { } diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs new file mode 100644 index 0000000000..1a9c84630d --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs @@ -0,0 +1,341 @@ +// +using System; +using Backbone.Modules.Synchronization.Infrastructure.Persistence.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Backbone.Modules.Synchronization.Infrastructure.Database.Postgres.Migrations +{ + [DbContext(typeof(SynchronizationDbContext))] + [Migration("20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable")] + partial class AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Synchronization") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("Version") + .IsUnicode(false) + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Owner") + .IsUnique(); + + b.ToTable("Datawallets", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.DatawalletModification", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Collection") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("DatawalletId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("DatawalletVersion") + .IsUnicode(false) + .HasColumnType("integer"); + + b.Property("EncryptedPayload") + .HasColumnType("bytea"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("ObjectIdentifier") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PayloadCategory") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DatawalletId"); + + b.HasIndex("CreatedBy", "Index") + .IsUnique(); + + b.ToTable("DatawalletModifications", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Context") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("IsDeliveryBlocked") + .HasColumnType("boolean"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("Payload") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SyncErrorCount") + .HasColumnType("smallint"); + + b.Property("SyncRunId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SyncRunId"); + + b.HasIndex("Owner", "Index") + .IsUnique(); + + b.HasIndex("Owner", "SyncRunId"); + + b.ToTable("ExternalEvents", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncError", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("ErrorCode") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ExternalEventId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("SyncRunId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("ExternalEventId"); + + b.HasIndex("SyncRunId", "ExternalEventId") + .IsUnique(); + + b.ToTable("SyncErrors", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("EventCount") + .HasColumnType("integer"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FinalizedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy", "FinalizedAt"); + + b.HasIndex("CreatedBy", "Index") + .IsUnique(); + + b.ToTable("SyncRuns", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.DatawalletModification", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", "Datawallet") + .WithMany("Modifications") + .HasForeignKey("DatawalletId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Datawallet"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", "SyncRun") + .WithMany("ExternalEvents") + .HasForeignKey("SyncRunId"); + + b.Navigation("SyncRun"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncError", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", null) + .WithMany("Errors") + .HasForeignKey("ExternalEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", null) + .WithMany("Errors") + .HasForeignKey("SyncRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", b => + { + b.Navigation("Modifications"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.Navigation("Errors"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", b => + { + b.Navigation("Errors"); + + b.Navigation("ExternalEvents"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs new file mode 100644 index 0000000000..8f7ea3cdb5 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/20241016072722_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Synchronization.Infrastructure.Database.Postgres.Migrations +{ + /// + public partial class AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Context", + schema: "Synchronization", + table: "ExternalEvents", + type: "character varying(20)", + maxLength: 20, + nullable: true); + + migrationBuilder.AddColumn( + name: "IsDeliveryBlocked", + schema: "Synchronization", + table: "ExternalEvents", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Context", + schema: "Synchronization", + table: "ExternalEvents"); + + migrationBuilder.DropColumn( + name: "IsDeliveryBlocked", + schema: "Synchronization", + table: "ExternalEvents"); + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/SynchronizationDbContextModelSnapshot.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/SynchronizationDbContextModelSnapshot.cs index 4297397e33..ee5c660ccd 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/SynchronizationDbContextModelSnapshot.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Migrations/SynchronizationDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Synchronization") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -118,6 +118,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DatawalletModifications", "Synchronization"); }); + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => { b.Property("Id") @@ -126,12 +145,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character(20)") .IsFixedLength(); + b.Property("Context") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); b.Property("Index") .HasColumnType("bigint"); + b.Property("IsDeliveryBlocked") + .HasColumnType("boolean"); + b.Property("Owner") .IsRequired() .HasMaxLength(80) diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs new file mode 100644 index 0000000000..51ecafc49e --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.Designer.cs @@ -0,0 +1,341 @@ +// +using System; +using Backbone.Modules.Synchronization.Infrastructure.Persistence.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Backbone.Modules.Synchronization.Infrastructure.Database.SqlServer.Migrations +{ + [DbContext(typeof(SynchronizationDbContext))] + [Migration("20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable")] + partial class AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Synchronization") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("Version") + .IsUnicode(false) + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Owner") + .IsUnique(); + + b.ToTable("Datawallets", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.DatawalletModification", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Collection") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("DatawalletId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("DatawalletVersion") + .IsUnicode(false) + .HasColumnType("int"); + + b.Property("EncryptedPayload") + .HasColumnType("varbinary(max)"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("ObjectIdentifier") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PayloadCategory") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DatawalletId"); + + b.HasIndex("CreatedBy", "Index") + .IsUnique(); + + b.ToTable("DatawalletModifications", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Context") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("IsDeliveryBlocked") + .HasColumnType("bit"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("Payload") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SyncErrorCount") + .HasColumnType("tinyint"); + + b.Property("SyncRunId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("SyncRunId"); + + b.HasIndex("Owner", "Index") + .IsUnique(); + + b.HasIndex("Owner", "SyncRunId"); + + b.ToTable("ExternalEvents", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncError", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("ErrorCode") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ExternalEventId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("SyncRunId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("ExternalEventId"); + + b.HasIndex("SyncRunId", "ExternalEventId") + .IsUnique(); + + b.ToTable("SyncErrors", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("EventCount") + .HasColumnType("int"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("FinalizedAt") + .HasColumnType("datetime2"); + + b.Property("Index") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy", "FinalizedAt"); + + b.HasIndex("CreatedBy", "Index") + .IsUnique(); + + b.ToTable("SyncRuns", "Synchronization"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.DatawalletModification", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", "Datawallet") + .WithMany("Modifications") + .HasForeignKey("DatawalletId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Datawallet"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", "SyncRun") + .WithMany("ExternalEvents") + .HasForeignKey("SyncRunId"); + + b.Navigation("SyncRun"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncError", b => + { + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", null) + .WithMany("Errors") + .HasForeignKey("ExternalEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", null) + .WithMany("Errors") + .HasForeignKey("SyncRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Datawallet", b => + { + b.Navigation("Modifications"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => + { + b.Navigation("Errors"); + }); + + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.SyncRun", b => + { + b.Navigation("Errors"); + + b.Navigation("ExternalEvents"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs new file mode 100644 index 0000000000..99f582ba4e --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/20241016072720_AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Synchronization.Infrastructure.Database.SqlServer.Migrations +{ + /// + public partial class AddIsDeliveryBlockedAndContextColumnsToExternalEventsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Context", + schema: "Synchronization", + table: "ExternalEvents", + type: "nvarchar(20)", + maxLength: 20, + nullable: true); + + migrationBuilder.AddColumn( + name: "IsDeliveryBlocked", + schema: "Synchronization", + table: "ExternalEvents", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Context", + schema: "Synchronization", + table: "ExternalEvents"); + + migrationBuilder.DropColumn( + name: "IsDeliveryBlocked", + schema: "Synchronization", + table: "ExternalEvents"); + } + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/SynchronizationDbContextModelSnapshot.cs b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/SynchronizationDbContextModelSnapshot.cs index 3411479ec8..3eae81af60 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/SynchronizationDbContextModelSnapshot.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Migrations/SynchronizationDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Synchronization") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -118,6 +118,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DatawalletModifications", "Synchronization"); }); + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Relationships", "Relationships", t => + { + t.ExcludeFromMigrations(); + }); + }); + modelBuilder.Entity("Backbone.Modules.Synchronization.Domain.Entities.Sync.ExternalEvent", b => { b.Property("Id") @@ -126,12 +145,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("char(20)") .IsFixedLength(); + b.Property("Context") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + b.Property("CreatedAt") .HasColumnType("datetime2"); b.Property("Index") .HasColumnType("bigint"); + b.Property("IsDeliveryBlocked") + .HasColumnType("bit"); + b.Property("Owner") .IsRequired() .HasMaxLength(80) diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/ExternalEventEntityTypeConfiguration.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/ExternalEventEntityTypeConfiguration.cs index e9364fa86b..19be3e03c0 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/ExternalEventEntityTypeConfiguration.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/ExternalEventEntityTypeConfiguration.cs @@ -22,5 +22,9 @@ public override void Configure(EntityTypeBuilder builder) builder.Property(x => x.Payload) .HasMaxLength(200) .HasConversion(); + + builder.Property(x => x.Context).HasMaxLength(20); + + builder.Property(x => x.IsDeliveryBlocked); } } diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/RelationshipEntityTypeConfiguration.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/RelationshipEntityTypeConfiguration.cs new file mode 100644 index 0000000000..06cb406b3a --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/Configurations/RelationshipEntityTypeConfiguration.cs @@ -0,0 +1,19 @@ +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Backbone.Modules.Synchronization.Infrastructure.Persistence.Database.Configurations; + +public class RelationshipEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Relationships", "Relationships", x => x.ExcludeFromMigrations()); + + builder.HasKey(x => x.Id); + + builder.Property(x => x.Status); + builder.Property(x => x.From); + builder.Property(x => x.To); + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/SynchronizationDbContext.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/SynchronizationDbContext.cs index 21d7b7fb08..33b0e51d79 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/SynchronizationDbContext.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/SynchronizationDbContext.cs @@ -9,6 +9,7 @@ using Backbone.Modules.Synchronization.Application.Extensions; using Backbone.Modules.Synchronization.Application.Infrastructure; using Backbone.Modules.Synchronization.Domain.Entities; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; using Backbone.Modules.Synchronization.Domain.Entities.Sync; using Backbone.Modules.Synchronization.Infrastructure.Persistence.Database.ValueConverters; using Microsoft.Data.SqlClient; @@ -38,6 +39,7 @@ public SynchronizationDbContext(DbContextOptions optio public DbSet ExternalEvents { get; set; } = null!; public DbSet SyncRuns { get; set; } = null!; public DbSet SyncErrors { get; set; } = null!; + public DbSet Relationships { get; set; } = null!; public async Task> GetDatawalletModifications(IdentityAddress activeIdentity, long? localIndex, PaginationFilter paginationFilter, CancellationToken cancellationToken) @@ -110,6 +112,15 @@ await RunInTransaction(async () => }, [DbErrorCodes.SQLSERVER_INDEX_ALREADY_EXISTS, DbErrorCodes.POSTGRES_INDEX_ALREADY_EXISTS]); } + public async Task DeleteUnsyncedExternalEventsWithOwnerAndContext(IdentityAddress owner, string context) + { + await Set() + .Unsynced() + .WithOwner(owner) + .WithContext(context) + .ExecuteDeleteAsync(); + } + public async Task GetSyncRun(SyncRunId syncRunId, IdentityAddress createdBy, CancellationToken cancellationToken) { return await SyncRuns @@ -162,6 +173,7 @@ public async Task> GetUnsyncedExternalEvents(IdentityAddress var unsyncedEvents = await ExternalEvents .WithOwner(owner) .Unsynced() + .NotBlocked() .WithErrorCountBelow(maxErrorCount) .ToListAsync(cancellationToken); @@ -178,6 +190,17 @@ public async Task> GetExternalEventsOfSyncRun( return query; } + public async Task> GetBlockedExternalEventsWithTypeAndContext(ExternalEventType type, string context, CancellationToken cancellationToken) + { + var query = await ExternalEvents + .Blocked() + .WithType(type) + .WithContext(context) + .ToListAsync(cancellationToken); + + return query; + } + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { base.ConfigureConventions(configurationBuilder); @@ -189,6 +212,7 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura configurationBuilder.Properties().AreUnicode(false).AreFixedLength().HaveMaxLength(SyncRunId.MAX_LENGTH).HaveConversion(); configurationBuilder.Properties().AreUnicode(false).AreFixedLength().HaveMaxLength(ExternalEventId.MAX_LENGTH).HaveConversion(); configurationBuilder.Properties().AreUnicode(false).AreFixedLength().HaveMaxLength(SyncErrorId.MAX_LENGTH).HaveConversion(); + configurationBuilder.Properties().AreUnicode(false).AreFixedLength().HaveMaxLength(RelationshipId.MAX_LENGTH).HaveConversion(); } protected override void OnModelCreating(ModelBuilder builder) diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/ExternalEventPayloadEntityFrameworkValueConverter.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/ExternalEventPayloadEntityFrameworkValueConverter.cs index 6ffd99d07d..fcc455c068 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/ExternalEventPayloadEntityFrameworkValueConverter.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/ExternalEventPayloadEntityFrameworkValueConverter.cs @@ -3,12 +3,10 @@ using Newtonsoft.Json; namespace Backbone.Modules.Synchronization.Infrastructure.Persistence.Database.ValueConverters; - public class ExternalEventPayloadEntityFrameworkValueConverter : ValueConverter { public ExternalEventPayloadEntityFrameworkValueConverter() : base( o => JsonConvert.SerializeObject(o), s => JsonConvert.DeserializeObject(s)!) - { - } + { } } diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/RelationshipIdEntityFrameworkValueConverter.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/RelationshipIdEntityFrameworkValueConverter.cs new file mode 100644 index 0000000000..08de44a637 --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Database/ValueConverters/RelationshipIdEntityFrameworkValueConverter.cs @@ -0,0 +1,20 @@ +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Backbone.Modules.Synchronization.Infrastructure.Persistence.Database.ValueConverters; + +public class RelationshipIdEntityFrameworkValueConverter : ValueConverter +{ + public RelationshipIdEntityFrameworkValueConverter() : this(new ConverterMappingHints(RelationshipId.MAX_LENGTH)) + { + } + + public RelationshipIdEntityFrameworkValueConverter(ConverterMappingHints mappingHints) + : base( + id => id.Value, + value => RelationshipId.Parse(value), + mappingHints.With(new ConverterMappingHints(RelationshipId.MAX_LENGTH)) + ) + { + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/IServiceCollectionExtensions.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/IServiceCollectionExtensions.cs index 74047524f6..7116b5dd80 100644 --- a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/IServiceCollectionExtensions.cs +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/IServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Backbone.Modules.Synchronization.Infrastructure.Persistence.Database; +using Backbone.Modules.Synchronization.Infrastructure.Persistence.Repository; using Microsoft.Extensions.DependencyInjection; namespace Backbone.Modules.Synchronization.Infrastructure.Persistence; @@ -16,6 +17,7 @@ public static void AddPersistence(this IServiceCollection services, Action(); + } +} diff --git a/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Repository/RelationshipsRepository.cs b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Repository/RelationshipsRepository.cs new file mode 100644 index 0000000000..ef7b5abdeb --- /dev/null +++ b/Modules/Synchronization/src/Synchronization.Infrastructure/Persistence/Repository/RelationshipsRepository.cs @@ -0,0 +1,21 @@ +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; +using Backbone.Modules.Synchronization.Infrastructure.Persistence.Database; +using Microsoft.EntityFrameworkCore; + +namespace Backbone.Modules.Synchronization.Infrastructure.Persistence.Repository; + +public class RelationshipsRepository : IRelationshipsRepository +{ + private readonly IQueryable _relationshipsReadOnly; + + public RelationshipsRepository(SynchronizationDbContext dbContext) + { + _relationshipsReadOnly = dbContext.Relationships.AsNoTracking(); + } + + public async Task> GetRelationships(IEnumerable ids, CancellationToken cancellationToken) + { + return await _relationshipsReadOnly.Where(r => ids.Contains(r.Id)).ToListAsync(cancellationToken); + } +} diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/ExternalEventBuilder.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/ExternalEventBuilder.cs index 04101f65e9..335187c872 100644 --- a/Modules/Synchronization/test/Synchronization.Application.Tests/ExternalEventBuilder.cs +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/ExternalEventBuilder.cs @@ -1,4 +1,5 @@ using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; using Backbone.Modules.Synchronization.Domain.Entities.Sync; namespace Backbone.Modules.Synchronization.Application.Tests; @@ -57,7 +58,7 @@ public ExternalEventBuilder WithIndex(int index) public ExternalEvent Create() { - var externalEvent = new MessageReceivedExternalEvent(_owner, new MessageReceivedExternalEvent.EventPayload { Id = "MSG11111111111111111" }) + var externalEvent = new MessageReceivedExternalEvent(_owner, new MessageReceivedExternalEvent.EventPayload { Id = "MSG11111111111111111" }, RelationshipId.New()) { SyncErrorCount = _errorCount }; diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/GlobalUsings.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/GlobalUsings.cs new file mode 100644 index 0000000000..2dc5e5a074 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using Backbone.UnitTestTools.BaseClasses; +global using FluentAssertions; +global using Xunit; +global using static Backbone.UnitTestTools.Data.TestDataGenerator; diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/MessageCreatedDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/MessageCreatedDomainEventHandlerTests.cs new file mode 100644 index 0000000000..ce6740cb30 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/MessageCreatedDomainEventHandlerTests.cs @@ -0,0 +1,121 @@ +using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.MessageCreated; +using Backbone.Modules.Synchronization.Application.Infrastructure; +using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.MessageCreated; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; +using Backbone.Modules.Synchronization.Domain.Entities.Sync; +using Backbone.Tooling; +using FakeItEasy; +using Microsoft.Extensions.Logging; + +namespace Backbone.Modules.Synchronization.Application.Tests.Tests.DomainEvents; + +public class MessageCreatedDomainEventHandlerTests : AbstractTestsBase +{ + [Fact] + public async Task Creates_an_external_event_for_each_recipient() + { + // Arrange + var senderAddress = CreateRandomIdentityAddress(); + var recipient1Address = CreateRandomIdentityAddress(); + var recipient2Address = CreateRandomIdentityAddress(); + + var relationshipToRecipient1 = new Relationship(new RelationshipId("REL11111111111111111"), senderAddress, recipient1Address, RelationshipStatus.Active); + var relationshipToRecipient2 = new Relationship(new RelationshipId("REL22222222222222222"), senderAddress, recipient2Address, RelationshipStatus.Active); + + var mockSynchronizationDbContext = A.Fake(); + var fakeRelationshipsRepository = RelationshipsRepositoryReturning([relationshipToRecipient1, relationshipToRecipient2]); + + var handler = CreateHandler(mockSynchronizationDbContext, fakeRelationshipsRepository); + + // Act + await handler.Handle(new MessageCreatedDomainEvent + { + Id = "MSG11111111111111111", + CreatedBy = senderAddress, + CreationDate = SystemTime.UtcNow, + Recipients = + [ + new MessageCreatedDomainEvent.Recipient + { + Address = recipient1Address, + RelationshipId = relationshipToRecipient1.Id + }, + new MessageCreatedDomainEvent.Recipient + { + Address = recipient2Address, + RelationshipId = relationshipToRecipient2.Id + } + ] + }); + + // Assert + A.CallTo(() => mockSynchronizationDbContext.CreateExternalEvent( + A.That.Matches( + e => e.Owner == recipient1Address && + e.Type == ExternalEventType.MessageReceived))) + .MustHaveHappenedOnceExactly(); + + A.CallTo(() => mockSynchronizationDbContext.CreateExternalEvent( + A.That.Matches( + e => e.Owner == recipient2Address && + e.Type == ExternalEventType.MessageReceived))) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task Created_external_events_are_blocked_when_relationship_with_recipient_is_in_status_Terminated() + { + // Arrange + var senderAddress = CreateRandomIdentityAddress(); + var recipientAddress = CreateRandomIdentityAddress(); + + var relationshipToRecipient = new Relationship(new RelationshipId("REL11111111111111111"), senderAddress, recipientAddress, RelationshipStatus.Terminated); + + var mockSynchronizationDbContext = A.Fake(); + var fakeRelationshipsRepository = RelationshipsRepositoryReturning([relationshipToRecipient]); + + var handler = CreateHandler(mockSynchronizationDbContext, fakeRelationshipsRepository); + + // Act + await handler.Handle(new MessageCreatedDomainEvent + { + Id = "MSG11111111111111111", + CreatedBy = senderAddress, + CreationDate = SystemTime.UtcNow, + Recipients = + [ + new MessageCreatedDomainEvent.Recipient + { + Address = recipientAddress, + RelationshipId = relationshipToRecipient.Id + } + ] + }); + + // Assert + A.CallTo(() => mockSynchronizationDbContext.CreateExternalEvent( + A.That.Matches( + e => e.Owner == recipientAddress && + e.Type == ExternalEventType.MessageReceived && + e.IsDeliveryBlocked))) + .MustHaveHappenedOnceExactly(); + } + + private IRelationshipsRepository RelationshipsRepositoryReturning(List relationships) + { + var relationshipIds = relationships.Select(r => r.Id); + + var relationshipsRepository = A.Fake(); + + A.CallTo(() => relationshipsRepository.GetRelationships(relationshipIds, A._)) + .Returns(relationships.ToList()); + + return relationshipsRepository; + } + + private MessageCreatedDomainEventHandler CreateHandler(ISynchronizationDbContext dbContext, IRelationshipsRepository relationshipsRepository) + { + var logger = A.Dummy>(); + return new MessageCreatedDomainEventHandler(dbContext, relationshipsRepository, logger); + } +} diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipReactivationCompletedDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipReactivationCompletedDomainEventHandlerTests.cs index 933210dea1..be8996949e 100644 --- a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipReactivationCompletedDomainEventHandlerTests.cs +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipReactivationCompletedDomainEventHandlerTests.cs @@ -1,6 +1,7 @@ using Backbone.Modules.Synchronization.Application.DomainEvents.Incoming.RelationshipReactivationCompleted; using Backbone.Modules.Synchronization.Application.Infrastructure; using Backbone.Modules.Synchronization.Domain.DomainEvents.Incoming.RelationshipReactivationCompleted; +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; using Backbone.Modules.Synchronization.Domain.Entities.Sync; using FakeItEasy; using Microsoft.Extensions.Logging; @@ -14,12 +15,11 @@ public async Task Creates_an_external_event() { // Arrange var identityAddress = CreateRandomIdentityAddress(); - var relationshipReactivationCompletedIntegrationEvent = new RelationshipReactivationCompletedDomainEvent("someRelationshipId", identityAddress); + var relationshipReactivationCompletedIntegrationEvent = CreateReactivationCompletedDomainEventForRelationship(RelationshipId.New()); var mockDbContext = A.Fake(); - var handler = new RelationshipReactivationCompletedDomainEventHandler(mockDbContext, - A.Fake>()); + var handler = CreateHandler(mockDbContext); // Act await handler.Handle(relationshipReactivationCompletedIntegrationEvent); @@ -27,4 +27,48 @@ public async Task Creates_an_external_event() // Assert A.CallTo(() => mockDbContext.CreateExternalEvent(A._)).MustHaveHappenedOnceExactly(); } + + [Fact] + public async Task Unblocks_MessageReceivedExternalEvents() + { + // Arrange + var idOfReactivatedRelationship = RelationshipId.New(); + + var relationshipReactivationCompletedIntegrationEvent = CreateReactivationCompletedDomainEventForRelationship(idOfReactivatedRelationship); + + var mockDbContext = A.Fake(); + + var messageReceivedExternalEvent = + new MessageReceivedExternalEvent(CreateRandomIdentityAddress(), new MessageReceivedExternalEvent.EventPayload { Id = "MSG11111111111111111" }, idOfReactivatedRelationship); + messageReceivedExternalEvent.BlockDelivery(); + + A.CallTo(() => mockDbContext.GetBlockedExternalEventsWithTypeAndContext(ExternalEventType.MessageReceived, A._, A._)) + .Returns([messageReceivedExternalEvent]); + + var handler = CreateHandler(mockDbContext); + + // Act + await handler.Handle(relationshipReactivationCompletedIntegrationEvent); + + // Assert + messageReceivedExternalEvent.IsDeliveryBlocked.Should().BeFalse(); + A.CallTo(() => mockDbContext.SaveChangesAsync(A._)).MustHaveHappenedOnceExactly(); + } + + private static RelationshipReactivationCompletedDomainEvent CreateReactivationCompletedDomainEventForRelationship(RelationshipId idOfReactivatedRelationship) + { + return new RelationshipReactivationCompletedDomainEvent + { + NewRelationshipStatus = "Active", + RelationshipId = idOfReactivatedRelationship, + Peer = CreateRandomIdentityAddress() + }; + } + + private static RelationshipReactivationCompletedDomainEventHandler CreateHandler(ISynchronizationDbContext dbContext) + { + var logger = A.Dummy>(); + + return new RelationshipReactivationCompletedDomainEventHandler(dbContext, logger); + } } diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipStatusChangedDomainEventHandlerTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipStatusChangedDomainEventHandlerTests.cs index 9ad35e5af9..f9f8e7d0b0 100644 --- a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipStatusChangedDomainEventHandlerTests.cs +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/DomainEvents/RelationshipStatusChangedDomainEventHandlerTests.cs @@ -18,7 +18,8 @@ public async Task Creates_an_external_event() { RelationshipId = "REL1", Peer = relationshipTo, - NewStatus = "Pending" + NewStatus = "Pending", + Initiator = CreateRandomIdentityAddress() }; var mockDbContext = A.Fake(); @@ -41,7 +42,8 @@ public async Task Does_not_create_an_external_event_if_new_status_is_ReadyForDel { RelationshipId = "REL1", Peer = relationshipTo, - NewStatus = "ReadyForDeletion" + NewStatus = "ReadyForDeletion", + Initiator = CreateRandomIdentityAddress() }; var mockDbContext = A.Fake(); @@ -55,6 +57,34 @@ public async Task Does_not_create_an_external_event_if_new_status_is_ReadyForDel A.CallTo(() => mockDbContext.CreateExternalEvent(A._)).MustNotHaveHappened(); } + [Theory] + [InlineData("DeletionProposed")] + [InlineData("ReadyForDeletion")] + public async Task Calls_DeleteUnsyncedExternalEventsWithOwnerAndContext_when_new_status_is_DeletionProposed_or_ReadyForDeletion(string newStatus) + { + // Arrange + var relationshipTo = CreateRandomIdentityAddress(); + const string relationshipId = "REL11111111111111111"; + var initiator = CreateRandomIdentityAddress(); + var @event = new RelationshipStatusChangedDomainEvent + { + RelationshipId = relationshipId, + Peer = relationshipTo, + NewStatus = newStatus, + Initiator = initiator + }; + + var mockDbContext = A.Fake(); + + var handler = CreateHandler(mockDbContext); + + // Act + await handler.Handle(@event); + + // Assert + A.CallTo(() => mockDbContext.DeleteUnsyncedExternalEventsWithOwnerAndContext(initiator, relationshipId)).MustHaveHappenedOnceExactly(); + } + private static RelationshipStatusChangedDomainEventHandler CreateHandler(ISynchronizationDbContext dbContext) { return new RelationshipStatusChangedDomainEventHandler(dbContext, A.Fake>()); diff --git a/Modules/Synchronization/test/Synchronization.Domain.Tests/GlobalUsings.cs b/Modules/Synchronization/test/Synchronization.Domain.Tests/GlobalUsings.cs new file mode 100644 index 0000000000..2dc5e5a074 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Domain.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using Backbone.UnitTestTools.BaseClasses; +global using FluentAssertions; +global using Xunit; +global using static Backbone.UnitTestTools.Data.TestDataGenerator; diff --git a/Modules/Synchronization/test/Synchronization.Domain.Tests/RelationshipTests.cs b/Modules/Synchronization/test/Synchronization.Domain.Tests/RelationshipTests.cs new file mode 100644 index 0000000000..12b84c5cd7 --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Domain.Tests/RelationshipTests.cs @@ -0,0 +1,40 @@ +using Backbone.Modules.Synchronization.Domain.Entities.Relationships; + +namespace Backbone.Modules.Synchronization.Domain.Tests; + +public class RelationshipTests : AbstractTestsBase +{ + [Fact] + public void IsBetween_returns_true_when_both_are_part_of_the_relationship() + { + // Arrange + var relationship = new Relationship(RelationshipId.New(), CreateRandomIdentityAddress(), CreateRandomIdentityAddress(), RelationshipStatus.Active); + + // Act + var result1 = relationship.IsBetween(relationship.From, relationship.To); + var result2 = relationship.IsBetween(relationship.To, relationship.From); + + // Assert + result1.Should().BeTrue(); + result2.Should().BeTrue(); + } + + [Fact] + public void IsBetween_returns_false_when_at_least_one_is_not_part_of_the_relationship() + { + // Arrange + var from = CreateRandomIdentityAddress(); + var to = CreateRandomIdentityAddress(); + var relationship = new Relationship(RelationshipId.New(), from, to, RelationshipStatus.Active); + + // Act + var result1 = relationship.IsBetween(CreateRandomIdentityAddress(), to); + var result2 = relationship.IsBetween(from, CreateRandomIdentityAddress()); + var result3 = relationship.IsBetween(CreateRandomIdentityAddress(), CreateRandomIdentityAddress()); + + // Assert + result1.Should().BeFalse(); + result2.Should().BeFalse(); + result3.Should().BeFalse(); + } +} diff --git a/Sdks/ConsumerApi.Sdk/src/Client.cs b/Sdks/ConsumerApi.Sdk/src/Client.cs index f8485b1ebc..36fceaf0a7 100644 --- a/Sdks/ConsumerApi.Sdk/src/Client.cs +++ b/Sdks/ConsumerApi.Sdk/src/Client.cs @@ -1,4 +1,5 @@ -using Backbone.BuildingBlocks.SDK.Crypto; +using System.Text.Json; +using Backbone.BuildingBlocks.SDK.Crypto; using Backbone.BuildingBlocks.SDK.Endpoints.Common; using Backbone.ConsumerApi.Sdk.Authentication; using Backbone.ConsumerApi.Sdk.Endpoints.Challenges; @@ -19,7 +20,6 @@ using Backbone.ConsumerApi.Sdk.Endpoints.Tokens; using Backbone.Crypto; using Backbone.Crypto.Implementations; -using Newtonsoft.Json; namespace Backbone.ConsumerApi.Sdk; @@ -167,7 +167,7 @@ public static async Task CreateForNewIdentity(HttpClient httpClient, Cli ClientSecret = client._configuration.Authentication.ClientCredentials.ClientSecret, IdentityVersion = 1, SignedChallenge = signedChallenge, - IdentityPublicKey = ConvertibleString.FromUtf8(JsonConvert.SerializeObject(new CryptoSignaturePublicKey + IdentityPublicKey = ConvertibleString.FromUtf8(JsonSerializer.Serialize(new CryptoSignaturePublicKey { alg = CryptoExchangeAlgorithm.ECDH_X25519, pub = keyPair.PublicKey.Base64Representation @@ -243,7 +243,7 @@ public async Task OnboardNewDevice(string password) throw new Exception( $"There was an error when creating a challenge for the new identity. The error code was '{createChallengeResponse.Error.Code}'. The message was '{createChallengeResponse.Error.Message}'."); - var serializedChallenge = JsonConvert.SerializeObject(createChallengeResponse.Result); + var serializedChallenge = JsonSerializer.Serialize(createChallengeResponse.Result); var challengeSignature = signatureHelper.CreateSignature(keyPair.PrivateKey, ConvertibleString.FromUtf8(serializedChallenge)); var signedChallenge = new SignedChallenge(serializedChallenge, challengeSignature); diff --git a/Sdks/ConsumerApi.Sdk/src/ConsumerApi.Sdk.csproj b/Sdks/ConsumerApi.Sdk/src/ConsumerApi.Sdk.csproj index 02bd282fdc..d5eda7fda3 100644 --- a/Sdks/ConsumerApi.Sdk/src/ConsumerApi.Sdk.csproj +++ b/Sdks/ConsumerApi.Sdk/src/ConsumerApi.Sdk.csproj @@ -10,9 +10,4 @@ - - - - - diff --git a/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs index 470b774cad..251b669840 100644 --- a/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs +++ b/Sdks/ConsumerApi.Sdk/src/Endpoints/Relationships/RelationshipsEndpoint.cs @@ -58,7 +58,7 @@ public async Task> TerminateRelationship(string relati return await _client.Put($"api/{API_VERSION}/Relationships/{relationshipId}/Terminate"); } - public async Task> RelationshipReactivationRequest(string relationshipId) + public async Task> ReactivateRelationship(string relationshipId) { return await _client.Put($"api/{API_VERSION}/Relationships/{relationshipId}/Reactivate"); } diff --git a/Sdks/ConsumerApi.Sdk/src/Endpoints/SyncRuns/Types/ExternalEvent.cs b/Sdks/ConsumerApi.Sdk/src/Endpoints/SyncRuns/Types/ExternalEvent.cs index 68bed121cb..6f62db5140 100644 --- a/Sdks/ConsumerApi.Sdk/src/Endpoints/SyncRuns/Types/ExternalEvent.cs +++ b/Sdks/ConsumerApi.Sdk/src/Endpoints/SyncRuns/Types/ExternalEvent.cs @@ -1,4 +1,6 @@ -namespace Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types; +using System.Text.Json; + +namespace Backbone.ConsumerApi.Sdk.Endpoints.SyncRuns.Types; public class ExternalEvent { @@ -7,5 +9,5 @@ public class ExternalEvent public required long Index { get; set; } public required DateTime CreatedAt { get; set; } public required byte SyncErrorCount { get; set; } - public required object Payload { get; set; } + public required Dictionary Payload { get; set; } } diff --git a/scripts/sql/postgres/setup.sql b/scripts/sql/postgres/setup.sql index 7dd2c4fe7f..90cdcd5e5a 100644 --- a/scripts/sql/postgres/setup.sql +++ b/scripts/sql/postgres/setup.sql @@ -174,6 +174,10 @@ GRANT USAGE ON SCHEMA "Messages" TO quotas; GRANT SELECT ON ALL TABLES IN SCHEMA "Messages" TO quotas; ALTER DEFAULT PRIVILEGES IN SCHEMA "Messages" GRANT SELECT ON TABLES TO quotas; +GRANT USAGE ON SCHEMA "Relationships" TO synchronization; +GRANT SELECT ON ALL TABLES IN SCHEMA "Relationships" TO synchronization; +ALTER DEFAULT PRIVILEGES IN SCHEMA "Relationships" GRANT SELECT ON TABLES TO synchronization; + GRANT USAGE ON SCHEMA "Relationships" TO quotas; GRANT SELECT ON ALL TABLES IN SCHEMA "Relationships" TO quotas; ALTER DEFAULT PRIVILEGES IN SCHEMA "Relationships" GRANT SELECT ON TABLES TO quotas; diff --git a/scripts/sql/sqlserver/setup.sql b/scripts/sql/sqlserver/setup.sql index e1c445b9dc..2ef4ce39a4 100644 --- a/scripts/sql/sqlserver/setup.sql +++ b/scripts/sql/sqlserver/setup.sql @@ -246,6 +246,7 @@ GRANT SELECT ON SCHEMA::Challenges TO quotas GRANT SELECT ON SCHEMA::Devices TO quotas GRANT SELECT ON SCHEMA::Files TO quotas GRANT SELECT ON SCHEMA::Messages TO quotas +GRANT SELECT ON SCHEMA::Relationships TO synchronization GRANT SELECT ON SCHEMA::Relationships TO quotas GRANT SELECT ON SCHEMA::Synchronization TO quotas GRANT SELECT ON SCHEMA::Tokens TO quotas