diff --git a/.ci/appsettings.override.postgres.json b/.ci/appsettings.override.postgres.docker.json similarity index 97% rename from .ci/appsettings.override.postgres.json rename to .ci/appsettings.override.postgres.docker.json index db969cf1ec..f149b8ccad 100644 --- a/.ci/appsettings.override.postgres.json +++ b/.ci/appsettings.override.postgres.docker.json @@ -4,17 +4,14 @@ }, "Authentication": { "ApiKey": "test", - "JwtSigningCertificate": "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", - "JwtLifetimeInSeconds": 300 + "JwtSigningCertificate": "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" }, "Infrastructure": { "EventBus": { "Vendor": "RabbitMQ", "ConnectionInfo": "rabbitmq", - "RabbitMQUsername": "guest", - "RabbitMQPassword": "guest", - "ConnectionRetryCount": 5 + "RabbitMQPassword": "guest" }, "SqlDatabase": { "Provider": "Postgres", diff --git a/.ci/appsettings.override.postgres.local.json b/.ci/appsettings.override.postgres.local.json new file mode 100644 index 0000000000..f201d25508 --- /dev/null +++ b/.ci/appsettings.override.postgres.local.json @@ -0,0 +1,118 @@ +{ + "Cors": { + "AccessControlAllowCredentials": true + }, + "Authentication": { + "ApiKey": "test", + "JwtSigningCertificate": "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" + }, + "Infrastructure": { + "EventBus": { + "Vendor": "RabbitMQ", + "ConnectionInfo": "localhost", + "RabbitMQUsername": "guest", + "RabbitMQPassword": "guest" + }, + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User Id=adminUi;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + }, + "Modules": { + "Challenges": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=challenges;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + }, + "Quotas": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=quotas;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + }, + "Devices": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=devices;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + }, + "PushNotifications": { + "Providers": { + "dummy": { + "enabled": true + } + } + } + } + }, + "Files": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=files;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + }, + "BlobStorage": { + "CloudProvider": "Azure", + "ConnectionInfo": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;" + } + } + }, + "Messages": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=messages;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + }, + "Relationships": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=relationships;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + }, + "Synchronization": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=synchronization;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + }, + "Tokens": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=tokens;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" + } + } + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug" + }, + "Enrich": ["FromLogContext", "WithProcessId", "WithThreadId"], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}|{Level} - CorrelationId:{CorrelationId} - RequestId:{RequestId} - RequestPath:{RequestPath}{NewLine} {SourceContext}{NewLine} {Message}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/.ci/appsettings.override.sqlserver.json b/.ci/appsettings.override.sqlserver.docker.json similarity index 98% rename from .ci/appsettings.override.sqlserver.json rename to .ci/appsettings.override.sqlserver.docker.json index 4729176977..c0671695df 100644 --- a/.ci/appsettings.override.sqlserver.json +++ b/.ci/appsettings.override.sqlserver.docker.json @@ -4,17 +4,14 @@ }, "Authentication": { "ApiKey": "test", - "JwtSigningCertificate": "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", - "JwtLifetimeInSeconds": 300 + "JwtSigningCertificate": "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" }, "Infrastructure": { "EventBus": { "Vendor": "RabbitMQ", "ConnectionInfo": "rabbitmq", - "RabbitMQUsername": "guest", - "RabbitMQPassword": "guest", - "ConnectionRetryCount": 5 + "RabbitMQPassword": "guest" }, "SqlDatabase": { "Provider": "SqlServer", diff --git a/.ci/appsettings.override.sqlserver.local.json b/.ci/appsettings.override.sqlserver.local.json new file mode 100644 index 0000000000..4a2b8120fe --- /dev/null +++ b/.ci/appsettings.override.sqlserver.local.json @@ -0,0 +1,118 @@ +{ + "Cors": { + "AccessControlAllowCredentials": true + }, + "Authentication": { + "ApiKey": "test", + "JwtSigningCertificate": "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" + }, + "Infrastructure": { + "EventBus": { + "Vendor": "RabbitMQ", + "ConnectionInfo": "localhost", + "RabbitMQUsername": "guest", + "RabbitMQPassword": "guest" + }, + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=adminUi;Password=Passw0rd;TrustServerCertificate=True" + } + }, + "Modules": { + "Challenges": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=challenges;Password=Passw0rd;TrustServerCertificate=True" + } + } + }, + "Quotas": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=quotas;Password=Passw0rd;TrustServerCertificate=True" + } + } + }, + "Devices": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=devices;Password=Passw0rd;TrustServerCertificate=True" + }, + "PushNotifications": { + "Providers": { + "dummy": { + "enabled": true + } + } + } + } + }, + "Files": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=files;Password=Passw0rd;TrustServerCertificate=True" + }, + "BlobStorage": { + "CloudProvider": "Azure", + "ConnectionInfo": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;" + } + } + }, + "Messages": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=messages;Password=Passw0rd;TrustServerCertificate=True" + } + } + }, + "Relationships": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=relationships;Password=Passw0rd;TrustServerCertificate=True" + } + } + }, + "Synchronization": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=synchronization;Password=Passw0rd;TrustServerCertificate=True" + } + } + }, + "Tokens": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "SqlServer", + "ConnectionString": "Server=localhost;Database=enmeshed;User Id=tokens;Password=Passw0rd;TrustServerCertificate=True" + } + } + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug" + }, + "Enrich": ["FromLogContext", "WithProcessId", "WithThreadId"], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}|{Level} - CorrelationId:{CorrelationId} - RequestId:{RequestId} - RequestPath:{RequestPath}{NewLine} {SourceContext}{NewLine} {Message}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/.ci/docker-compose.test.postgres.yml b/.ci/compose.test.postgres.yml similarity index 95% rename from .ci/docker-compose.test.postgres.yml rename to .ci/compose.test.postgres.yml index 3f3a183907..65149755dc 100644 --- a/.ci/docker-compose.test.postgres.yml +++ b/.ci/compose.test.postgres.yml @@ -36,7 +36,7 @@ services: configs: Config: - file: appsettings.override.postgres.json + file: appsettings.override.postgres.docker.json networks: default: diff --git a/.ci/docker-compose.test.sqlserver.yml b/.ci/compose.test.sqlserver.yml similarity index 95% rename from .ci/docker-compose.test.sqlserver.yml rename to .ci/compose.test.sqlserver.yml index 5fa2f2066b..dd06c1754e 100644 --- a/.ci/docker-compose.test.sqlserver.yml +++ b/.ci/compose.test.sqlserver.yml @@ -35,7 +35,7 @@ services: configs: Config: - file: appsettings.override.sqlserver.json + file: appsettings.override.sqlserver.docker.json networks: default: diff --git a/.ci/docker-compose.test.yml b/.ci/compose.test.yml similarity index 95% rename from .ci/docker-compose.test.yml rename to .ci/compose.test.yml index 391acf0b8f..e8795784bf 100644 --- a/.ci/docker-compose.test.yml +++ b/.ci/compose.test.yml @@ -54,6 +54,11 @@ services: image: sse-server:0.0.1 environment: - ASPNETCORE_ENVIRONMENT=Development + depends_on: + database-migrator: + condition: service_completed_successfully + rabbitmq: + condition: service_started configs: - source: Config target: app/appsettings.override.json diff --git a/.ci/integrationTest.postgres.sh b/.ci/integrationTest.postgres.sh deleted file mode 100755 index e25f3e6d80..0000000000 --- a/.ci/integrationTest.postgres.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -dockerCompose() { - docker compose -f ./.ci/docker-compose.test.yml -f ./.ci/docker-compose.test.postgres.yml "$@" -} - -{ - dockerCompose down; - dockerCompose up -d --no-build -} & -{ - dotnet restore "Backbone.sln"; - dotnet build --no-restore "Backbone.sln" -} -wait - -export CONSUMER_API_BASE_ADDRESS="http://localhost:5000" -export ADMIN_API_BASE_ADDRESS="http://localhost:5173" - -dotnet test --no-restore --no-build --filter "Category=Integration&TestCategory!~ignore" --logger "GitHubActions;summary.includeNotFoundTests=false" "Backbone.sln" diff --git a/.ci/integrationTest.sqlserver.sh b/.ci/integrationTest.sqlserver.sh deleted file mode 100755 index 8b9cf954a7..0000000000 --- a/.ci/integrationTest.sqlserver.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -dockerCompose() { - docker compose -f ./.ci/docker-compose.test.yml -f ./.ci/docker-compose.test.sqlserver.yml "$@" -} - -{ - dockerCompose down; - dockerCompose up -d --no-build -} & -{ - dotnet restore "Backbone.sln"; - dotnet build --no-restore "Backbone.sln" -} -wait - -export CONSUMER_API_BASE_ADDRESS="http://localhost:5000" -export ADMIN_API_BASE_ADDRESS="http://localhost:5173" - -dotnet test --no-restore --no-build --filter "Category=Integration&TestCategory!~ignore" --logger "GitHubActions;summary.includeNotFoundTests=false" "Backbone.sln" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34482a9db4..7c333d2605 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: true jobs: - run-adminUi-checks: + adminui-checks: runs-on: ubuntu-latest name: Run Admin UI Checks steps: @@ -21,7 +21,7 @@ jobs: - name: Run Checks run: ./.ci/aui/runChecks.sh - run-adminUi-flutter-checks: + adminui-flutter-checks: runs-on: ubuntu-latest name: Run Admin UI Flutter Checks steps: @@ -46,17 +46,13 @@ jobs: - name: Cache npm dependencies uses: actions/cache@v4 with: - path: | - ./runtime/node_modules + path: ./runtime/node_modules key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- - - name: Install script dependencies - run: npm install --prefix ./.ci + restore-keys: ${{ runner.os }}-npm- - name: Setup dotnet uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0.x" + dotnet-version: "8.x" - name: Cache NuGet packages uses: actions/cache@v4 with: @@ -80,17 +76,13 @@ jobs: - name: Cache npm dependencies uses: actions/cache@v4 with: - path: | - ./runtime/node_modules + path: ./runtime/node_modules key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- - - name: Install script dependencies - run: npm install --prefix ./.ci + restore-keys: ${{ runner.os }}-npm- - name: Setup dotnet uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0.x" + dotnet-version: "8.x" - name: Cache NuGet packages uses: actions/cache@v4 with: @@ -106,6 +98,7 @@ jobs: name: Build ${{matrix.image}} Image runs-on: ubuntu-latest strategy: + fail-fast: false matrix: include: - dockerfile: ConsumerApi/Dockerfile @@ -157,16 +150,12 @@ jobs: with: path: /tmp/.buildx-${{ matrix.image }}-cache key: buildx-${{ matrix.image }}-cache-${{ hashFiles(matrix.dockerfile) }} - restore-keys: | - buildx-${{ matrix.image }}-cache- - - - name: Install script dependencies - run: npm install --prefix ./.ci + restore-keys: buildx-${{ matrix.image }}-cache- - name: Build Image uses: docker/build-push-action@v6 env: - DOCKER_BUILD_NO_SUMMARY: true + DOCKER_BUILD_SUMMARY: false with: context: . file: ${{ matrix.dockerfile }} @@ -190,9 +179,20 @@ jobs: rm -rf /tmp/.buildx-${{ matrix.image }}-cache mv /tmp/.buildx-${{ matrix.image }}-cache-new /tmp/.buildx-${{ matrix.image }}-cache - integration-test-sqlserver: - name: Run Integration Tests (on SQL Server) + integration-test: + name: Run ${{matrix.test-project.display-name}} Integration Tests (on ${{matrix.database}}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + database: [sqlserver, postgres] + test-project: + - display-name: admin-api + path: AdminApi/test/AdminApi.Tests.Integration + - display-name: consumer-api + path: ConsumerApi.Tests.Integration + - display-name: identity-deletion-job + path: Jobs/test/Job.IdentityDeletion.Tests.Integration needs: image-test-builds steps: - name: Checkout @@ -209,56 +209,12 @@ jobs: with: dotnet-version: 8.x - - name: Install script dependencies - run: npm install --prefix ./.ci - - - name: Download cached Docker images - uses: actions/download-artifact@v4 - with: - path: /tmp - pattern: docker-* - merge-multiple: true - - - name: Load Docker images - run: ./.ci/loadDockerImages.sh - - - name: Run integration tests - run: ./.ci/integrationTest.sqlserver.sh - - - name: Save Docker Logs - if: failure() - run: docker compose -f ./.ci/docker-compose.test.yml -f ./.ci/docker-compose.test.sqlserver.yml logs > docker-log.txt - - - name: Archive logs - if: failure() - uses: actions/upload-artifact@v4 - with: - name: integration-test-sqlserver-docker-logs - path: docker-log.txt - env: - DOTNET_CONSOLE_ANSI_COLOR: true - - integration-test-postgres: - name: Run Integration Tests (on Postgres) - runs-on: ubuntu-latest - needs: image-test-builds - steps: - - name: Checkout - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: latest - cache: "npm" - cache-dependency-path: "**/package-lock.json" - - - name: Setup NuGet - uses: actions/setup-dotnet@v4 + - name: Cache NuGet packages + uses: actions/cache@v4 with: - dotnet-version: 8.x - - - name: Install script dependencies - run: npm install --prefix ./.ci + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + restore-keys: ${{ runner.os }}-nuget- - name: Download cached Docker images uses: actions/download-artifact@v4 @@ -267,92 +223,51 @@ jobs: pattern: docker-* merge-multiple: true - - name: Load Docker images - run: ./.ci/loadDockerImages.sh - - - name: Run integration tests - run: ./.ci/integrationTest.postgres.sh - - - name: Save Docker Logs - if: failure() - run: docker compose -f ./.ci/docker-compose.test.yml -f ./.ci/docker-compose.test.postgres.yml logs > docker-log.txt - - - name: Archive logs - if: failure() - uses: actions/upload-artifact@v4 - with: - name: integration-test-postgres-docker-logs - path: docker-log.txt - env: - DOTNET_CONSOLE_ANSI_COLOR: true - - transport-test-sqlserver: - name: Run transport Tests (on SQL Server) - runs-on: ubuntu-latest - needs: image-test-builds - steps: - - name: Checkout backbone repository - uses: actions/checkout@v4 - with: - path: backbone - - - name: Checkout runtime repository - uses: actions/checkout@v4 - with: - repository: nmshd/runtime - path: runtime - ref: release/v5 - - - uses: actions/setup-node@v4 - with: - node-version: latest - cache: "npm" - cache-dependency-path: "**/package-lock.json" - - - name: Download cached Docker images - uses: actions/download-artifact@v4 - with: - path: /tmp - pattern: docker-* - merge-multiple: true + - name: Load Docker images and build test code + run: | + { + ./.ci/loadDockerImages.sh + } & + { + # The following two lines are for the identity deletion job only + mv appsettings.override.json appsettings.override.json.bak + cp .ci/appsettings.override.${{matrix.database}}.local.json appsettings.override.json - - name: Load Docker images - run: ./backbone/.ci/loadDockerImages.sh + dotnet restore Backbone.sln - - name: Run Consumer API & Admin API - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.sqlserver.yml up -d --no-build + dotnet build --no-restore Backbone.sln + } + wait - - name: Install runtime dependencies - working-directory: ./runtime - run: npm install + - name: Start compose stack + run: | + docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database}}.yml down -v + docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database}}.yml up --no-build --wait -d - - name: Run transport Tests - working-directory: ./runtime/packages/transport + - name: Run integration tests + run: dotnet test --no-restore --no-build --logger "GitHubActions;summary.includeNotFoundTests=false;summary.includeSkippedTests=false;summary.includePassedTests=false" ${{matrix.test-project.path}} env: - NMSHD_TEST_BASEURL: http://localhost:5000 - NMSHD_TEST_CLIENTID: test - NMSHD_TEST_CLIENTSECRET: test - NMSHD_TEST_BASEURL_ADMIN_API: http://localhost:5173 - NMSHD_TEST_ADMIN_API_KEY: test - run: npm run test:local:lokijs + ADMIN_API_BASE_ADDRESS: "http://localhost:5173" + CONSUMER_API_BASE_ADDRESS: "http://localhost:5000" - name: Save Docker Logs if: failure() - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.sqlserver.yml logs > docker-log.txt + run: docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database}}.yml logs > logs.txt - name: Archive logs if: failure() uses: actions/upload-artifact@v4 with: - name: transport-test-sqlserver-docker-logs - path: docker-log.txt + name: ${{matrix.test-project.display-name}}-${{matrix.database}} + path: logs.txt - - name: Stop Consumer API - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.sqlserver.yml down - - transport-test-postgres: - name: Run transport Tests (on Postgres) + transport-test: + name: Run transport Tests (on ${{matrix.database}}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + database: [sqlserver, postgres] needs: image-test-builds steps: - name: Checkout backbone repository @@ -383,8 +298,8 @@ jobs: - name: Load Docker images run: ./backbone/.ci/loadDockerImages.sh - - name: Run Consumer API & Admin API - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.postgres.yml up -d --no-build + - name: Start compose stack + run: docker compose -f ./backbone/.ci/compose.test.yml -f ./backbone/.ci/compose.test.${{matrix.database}}.yml up --no-build --wait -d - name: Install runtime dependencies working-directory: ./runtime @@ -402,17 +317,17 @@ jobs: - name: Save Docker Logs if: failure() - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.postgres.yml logs > docker-log.txt + run: docker compose -f ./backbone/.ci/compose.test.yml -f ./backbone/.ci/compose.test.${{matrix.database}}.yml logs > logs.txt - name: Archive logs if: failure() uses: actions/upload-artifact@v4 with: - name: transport-test-postgres-docker-logs - path: docker-log.txt + name: transport-test-${{matrix.database}}-docker-logs + path: logs.txt - - name: Stop Consumer API - run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.postgres.yml down + - name: Stop compose stack + run: docker compose -f ./backbone/.ci/compose.test.${{matrix.database}}.yml -f ./backbone/.ci/compose.test.yml down build-helm-chart: name: Build Helm Chart diff --git a/AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntryDTO.cs b/AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntry.cs similarity index 71% rename from AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntryDTO.cs rename to AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntry.cs index 581398a193..5d705f93bb 100644 --- a/AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntryDTO.cs +++ b/AdminApi.Sdk/Endpoints/Identities/Types/IdentityDeletionProcessAuditLogEntry.cs @@ -1,10 +1,11 @@ namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types; -public class IdentityDeletionProcessAuditLogEntryDTO +public class IdentityDeletionProcessAuditLogEntry { public required string Id { get; set; } public required DateTime CreatedAt { get; set; } - public required string MessageKey { get; set; } public required string? OldStatus { get; set; } public required string NewStatus { get; set; } + public required string MessageKey { get; set; } + public required Dictionary AdditionalData { get; set; } } diff --git a/AdminApi.Sdk/Endpoints/Identities/Types/Responses/ListIdentityDeletionProcessAuditLogsResponse.cs b/AdminApi.Sdk/Endpoints/Identities/Types/Responses/ListIdentityDeletionProcessAuditLogsResponse.cs index a3c76e10d8..290cf111a0 100644 --- a/AdminApi.Sdk/Endpoints/Identities/Types/Responses/ListIdentityDeletionProcessAuditLogsResponse.cs +++ b/AdminApi.Sdk/Endpoints/Identities/Types/Responses/ListIdentityDeletionProcessAuditLogsResponse.cs @@ -2,4 +2,4 @@ namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types.Responses; -public class ListIdentityDeletionProcessAuditLogsResponse : EnumerableResponseBase; +public class ListIdentityDeletionProcessAuditLogsResponse : EnumerableResponseBase; diff --git a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.html b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.html index 50e7252684..b49b828881 100644 --- a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.html +++ b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.html @@ -35,7 +35,7 @@

{{ headerDeletionProcessAuditLog }}

Message - {{ replaceMessageKeyWithCorrespondingText(AuditLog.messageKey) }} + {{ getFormattedMessage(AuditLog.messageKey, AuditLog.additionalData) }} diff --git a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.ts b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.ts index 60acca5ed4..23cf1c7048 100644 --- a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.ts +++ b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-audit-logs/dp-audit-logs-details/dp-audit-logs-details/dp-audit-logs-details.component.ts @@ -67,36 +67,7 @@ export class DeletionProcessAuditLogsDetailsComponent implements OnInit { return status; } - public replaceMessageKeyWithCorrespondingText(messageKey: string): string { - switch (messageKey) { - case "StartedByOwner": - return "The deletion process was started by the owner. It was automatically approved."; - case "StartedBySupport": - return "The deletion process was started by support. It is now waiting for approval."; - case "Approved": - return "The deletion process was approved."; - case "Rejected": - return "The deletion process was rejected."; - case "CancelledByOwner": - return "The deletion process was cancelled by the owner of the identity."; - case "CancelledBySupport": - return "The deletion process was cancelled by a support employee."; - case "CancelledAutomatically": - return "The deletion process was cancelled automatically, because it wasn't approved by the owner within the approval period."; - case "ApprovalReminder1Sent": - return "The first approval reminder notification has been sent."; - case "ApprovalReminder2Sent": - return "The second approval reminder notification has been sent."; - case "ApprovalReminder3Sent": - return "The third approval reminder notification has been sent."; - case "GracePeriodReminder1Sent": - return "The first grace period reminder notification has been sent."; - case "GracePeriodReminder2Sent": - return "The second grace period reminder notification has been sent."; - case "GracePeriodReminder3Sent": - return "The third grace period reminder notification has been sent."; - default: - return "Unknown message key."; - } + public getFormattedMessage(messageKey: string, additionalData: Record): string { + return this.identityService.getMessageForDeletionProcessAuditLog(messageKey, additionalData); } } diff --git a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.html b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.html index c20a965d11..ecf861ae54 100644 --- a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.html +++ b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.html @@ -62,7 +62,7 @@

{{ header }}

Message - {{ replaceMessageKeyWithCorrespondingText(AuditLog.messageKey) }} + {{ getFormattedMessage(AuditLog.messageKey, AuditLog.additionalData) }} diff --git a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.ts b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.ts index 8e84e7a872..50c95730ea 100644 --- a/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.ts +++ b/AdminApi/src/AdminApi/ClientApp/src/app/components/identity/identity-details/deletion-processes/dp-details/dp-details.component.ts @@ -111,36 +111,7 @@ export class DeletionProcessDetailsComponent { }); } - public replaceMessageKeyWithCorrespondingText(messageKey: string): string { - switch (messageKey) { - case "StartedByOwner": - return "The deletion process was started by the owner. It was automatically approved."; - case "StartedBySupport": - return "The deletion process was started by support. It is now waiting for approval."; - case "Approved": - return "The deletion process was approved."; - case "Rejected": - return "The deletion process was rejected."; - case "CancelledByOwner": - return "The deletion process was cancelled by the owner of the identity."; - case "CancelledBySupport": - return "The deletion process was cancelled by a support employee."; - case "CancelledAutomatically": - return "The deletion process was cancelled automatically, because it wasn't approved by the owner within the approval period."; - case "ApprovalReminder1Sent": - return "The first approval reminder notification has been sent."; - case "ApprovalReminder2Sent": - return "The second approval reminder notification has been sent."; - case "ApprovalReminder3Sent": - return "The third approval reminder notification has been sent."; - case "GracePeriodReminder1Sent": - return "The first grace period reminder notification has been sent."; - case "GracePeriodReminder2Sent": - return "The second grace period reminder notification has been sent."; - case "GracePeriodReminder3Sent": - return "The third grace period reminder notification has been sent."; - default: - return "Unknown message key."; - } + public getFormattedMessage(messageKey: string, additionalData: Record): string { + return this.identityService.getMessageForDeletionProcessAuditLog(messageKey, additionalData); } } diff --git a/AdminApi/src/AdminApi/ClientApp/src/app/services/identity-service/identity.service.ts b/AdminApi/src/AdminApi/ClientApp/src/app/services/identity-service/identity.service.ts index 87131af94d..3099f75506 100644 --- a/AdminApi/src/AdminApi/ClientApp/src/app/services/identity-service/identity.service.ts +++ b/AdminApi/src/AdminApi/ClientApp/src/app/services/identity-service/identity.service.ts @@ -26,6 +26,40 @@ export class IdentityService { this.odataUrl = `${environment.odataUrl}/Identities`; } + /* eslint-disable @typescript-eslint/naming-convention */ + private readonly messageTemplates: Record = { + StartedByOwner: "The deletion process was started by the owner. It was automatically approved.", + StartedBySupport: "The deletion process was started by support. It is now waiting for approval.", + Approved: "The deletion process was approved.", + Rejected: "The deletion process was rejected.", + CancelledByOwner: "The deletion process was cancelled by the owner of the identity.", + CancelledBySupport: "The deletion process was cancelled by a support employee.", + CancelledAutomatically: "The deletion process was cancelled automatically, because it wasn't approved by the owner within the approval period.", + ApprovalReminder1Sent: "The first approval reminder notification has been sent.", + ApprovalReminder2Sent: "The second approval reminder notification has been sent.", + ApprovalReminder3Sent: "The third approval reminder notification has been sent.", + GracePeriodReminder1Sent: "The first grace period reminder notification has been sent.", + GracePeriodReminder2Sent: "The second grace period reminder notification has been sent.", + GracePeriodReminder3Sent: "The third grace period reminder notification has been sent.", + DataDeleted: "All {aggregateType} have been deleted." + }; + /* eslint-enable @typescript-eslint/naming-convention */ + + public getMessageForDeletionProcessAuditLog(messageKey: string, additionalData: Record): string { + let messageTemplate = this.messageTemplates[messageKey]; + + if (!messageTemplate) { + return "Unknown message key."; + } + + Object.keys(additionalData).forEach((key) => { + const placeholder = `{${key}}`; + messageTemplate = messageTemplate.replace(new RegExp(placeholder, "g"), additionalData[key]); + }); + + return messageTemplate; + } + public getIdentities(filter: IdentityOverviewFilter, pageNumber: number, pageSize: number): Observable> { const paginationFilter = `$top=${pageSize}&$skip=${pageNumber * pageSize}&$count=true`; return this.http.get(`${this.odataUrl}${this.buildODataFilter(filter, paginationFilter)}${this.buildODataExpand()}`).pipe( @@ -267,7 +301,8 @@ export interface UpdateTierRequest { export interface DeletionProcessAuditLog { id: string; createdAt: string; - message: string; + messageKey: string; + additionalData: Record; oldStatus: number; newStatus: number; } diff --git a/Backbone.sln b/Backbone.sln index 8387709526..d6febc45aa 100644 --- a/Backbone.sln +++ b/Backbone.sln @@ -309,6 +309,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{824495A9-A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SseServerTests", "SseServer\test\SseServerTests\SseServerTests.csproj", "{94A32246-991D-483D-8241-E1E4DD6DE145}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Challenges.Application.Tests", "Modules\Challenges\test\Challenges.Application.Tests\Challenges.Application.Tests.csproj", "{EAA10D43-BD28-40D8-BE07-B8F6DE47C156}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Job.IdentityDeletion.Tests.Integration", "Jobs\test\Job.IdentityDeletion.Tests.Integration\Job.IdentityDeletion.Tests.Integration.csproj", "{63BC08F3-5DA8-4AC5-877A-89B6ABA05F44}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Others", "Others", "{8B8EE965-0373-46D6-BB30-7C131EE524E4}" + ProjectSection(SolutionItems) = preProject + appsettings.override.json = appsettings.override.json + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -739,6 +749,14 @@ Global {94A32246-991D-483D-8241-E1E4DD6DE145}.Debug|Any CPU.Build.0 = Debug|Any CPU {94A32246-991D-483D-8241-E1E4DD6DE145}.Release|Any CPU.ActiveCfg = Release|Any CPU {94A32246-991D-483D-8241-E1E4DD6DE145}.Release|Any CPU.Build.0 = Release|Any CPU + {EAA10D43-BD28-40D8-BE07-B8F6DE47C156}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAA10D43-BD28-40D8-BE07-B8F6DE47C156}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAA10D43-BD28-40D8-BE07-B8F6DE47C156}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAA10D43-BD28-40D8-BE07-B8F6DE47C156}.Release|Any CPU.Build.0 = Release|Any CPU + {63BC08F3-5DA8-4AC5-877A-89B6ABA05F44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63BC08F3-5DA8-4AC5-877A-89B6ABA05F44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63BC08F3-5DA8-4AC5-877A-89B6ABA05F44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63BC08F3-5DA8-4AC5-877A-89B6ABA05F44}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -878,6 +896,8 @@ Global {434C078E-D738-4C19-AB89-5F56B57EDA7C} = {AAE548AB-4843-476A-BF61-002CF6FEE713} {824495A9-A255-487D-AF26-6F6CA92BC715} = {4C2E211B-37C8-4449-9595-7D019AC92AC3} {94A32246-991D-483D-8241-E1E4DD6DE145} = {824495A9-A255-487D-AF26-6F6CA92BC715} + {EAA10D43-BD28-40D8-BE07-B8F6DE47C156} = {EFC1F89E-1C44-4385-A0F6-1F2124260561} + {63BC08F3-5DA8-4AC5-877A-89B6ABA05F44} = {4F812E23-62EB-4B79-8ECA-5CA72CF5D3BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1F3BD2C6-7CB3-450F-A21A-23EA520D5B7A} diff --git a/BuildingBlocks/src/BuildingBlocks.Application/Identities/IDeletionProcessLogger.cs b/BuildingBlocks/src/BuildingBlocks.Application/Identities/IDeletionProcessLogger.cs new file mode 100644 index 0000000000..9ad952a4c7 --- /dev/null +++ b/BuildingBlocks/src/BuildingBlocks.Application/Identities/IDeletionProcessLogger.cs @@ -0,0 +1,7 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; + +namespace Backbone.BuildingBlocks.Application.Identities; +public interface IDeletionProcessLogger +{ + Task LogDeletion(IdentityAddress identityAddress, string aggregateType); +} diff --git a/BuildingBlocks/src/UnitTestTools/Extensions/ApplicationDbContextExtensions.cs b/BuildingBlocks/src/UnitTestTools/Extensions/ApplicationDbContextExtensions.cs new file mode 100644 index 0000000000..3543ab5e36 --- /dev/null +++ b/BuildingBlocks/src/UnitTestTools/Extensions/ApplicationDbContextExtensions.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; + +namespace Backbone.UnitTestTools.Extensions; + +public static class ApplicationDbContextExtensions +{ + public static async Task SaveEntities(this DbContext dbContext, params TEntity[] entities) where TEntity : class + { + dbContext.Set().AddRange(entities); + await dbContext.SaveChangesAsync(); + return entities; + } + + public static async Task SaveEntity(this DbContext dbContext, TEntity entity) where TEntity : class + { + dbContext.Set().Add(entity); + await dbContext.SaveChangesAsync(); + return entity; + } + + public static async Task RemoveEntity(this DbContext dbContext, TEntity entity) where TEntity : class + { + dbContext.Set().Remove(entity); + await dbContext.SaveChangesAsync(); + } +} diff --git a/Jobs/src/Job.IdentityDeletion/DeletionProcessLogger.cs b/Jobs/src/Job.IdentityDeletion/DeletionProcessLogger.cs new file mode 100644 index 0000000000..65e9dd533b --- /dev/null +++ b/Jobs/src/Job.IdentityDeletion/DeletionProcessLogger.cs @@ -0,0 +1,20 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Devices.Application.Identities.Commands.LogDeletionProcess; +using MediatR; + +namespace Backbone.Job.IdentityDeletion; +public class DeletionProcessLogger : IDeletionProcessLogger +{ + private readonly IMediator _mediator; + + public DeletionProcessLogger(IMediator mediator) + { + _mediator = mediator; + } + + public async Task LogDeletion(IdentityAddress identityAddress, string aggregateType) + { + await _mediator.Send(new LogDeletionProcessCommand(identityAddress, aggregateType)); + } +} diff --git a/Jobs/src/Job.IdentityDeletion/Program.cs b/Jobs/src/Job.IdentityDeletion/Program.cs index e125b605a2..ca31e3eaee 100644 --- a/Jobs/src/Job.IdentityDeletion/Program.cs +++ b/Jobs/src/Job.IdentityDeletion/Program.cs @@ -1,9 +1,9 @@ -using System.Reflection; +using System.Reflection; using Autofac.Extensions.DependencyInjection; using Backbone.BuildingBlocks.API.Extensions; +using Backbone.BuildingBlocks.Application.Identities; using Backbone.BuildingBlocks.Application.QuotaCheck; using Backbone.Infrastructure.EventBus; -using Backbone.Job.IdentityDeletion; using Backbone.Modules.Challenges.ConsumerApi; using Backbone.Modules.Devices.ConsumerApi; using Backbone.Modules.Devices.Infrastructure.PushNotifications; @@ -20,105 +20,113 @@ using Serilog.Exceptions.Core; using Serilog.Exceptions.EntityFrameworkCore.Destructurers; using Serilog.Settings.Configuration; -using DevicesConfiguration = Backbone.Modules.Devices.ConsumerApi.Configuration; +using Configuration = Backbone.Modules.Devices.ConsumerApi.Configuration; +namespace Backbone.Job.IdentityDeletion; -Log.Logger = new LoggerConfiguration() - .WriteTo.Console() - .CreateBootstrapLogger(); - -try +public class Program { - Log.Information("Creating app..."); - - var app = CreateHostBuilder(args); + public static async Task Main(params string[] args) + { + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); - Log.Information("App created."); - Log.Information("Starting app..."); + try + { + Log.Information("Creating app..."); - await app.Build().RunAsync(); + var app = CreateHostBuilder(args); - return 0; -} -catch (Exception ex) -{ - Log.Fatal(ex, "Host terminated unexpectedly"); - return 1; -} -finally -{ - await Log.CloseAndFlushAsync(); -} + Log.Information("App created."); + Log.Information("Starting app..."); + await app.Build().RunAsync(); -static IHostBuilder CreateHostBuilder(string[] args) -{ - return Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((hostContext, configuration) => + return 0; + } + catch (Exception ex) { - configuration.Sources.Clear(); - var env = hostContext.HostingEnvironment; - - configuration - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddJsonFile("appsettings.override.json", optional: true, reloadOnChange: true); - - if (env.IsDevelopment()) - { - var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); - configuration.AddUserSecrets(appAssembly, optional: true); - } - - configuration.AddEnvironmentVariables(); - configuration.AddCommandLine(args); - }) - .ConfigureServices((hostContext, services) => + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally { - var configuration = hostContext.Configuration; - services.ConfigureAndValidate(configuration.Bind); + await Log.CloseAndFlushAsync(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostContext, configuration) => + { + configuration.Sources.Clear(); + var env = hostContext.HostingEnvironment; + + configuration + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.override.json", optional: true, reloadOnChange: true); + + if (env.IsDevelopment()) + { + var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); + configuration.AddUserSecrets(appAssembly, optional: true); + } + + configuration.AddEnvironmentVariables(); + configuration.AddCommandLine(args); + }) + .ConfigureServices((hostContext, services) => + { + var configuration = hostContext.Configuration; + services.ConfigureAndValidate(configuration.Bind); #pragma warning disable ASP0000 // We retrieve the BackboneConfiguration via IOptions here so that it is validated - var parsedConfiguration = - services.BuildServiceProvider().GetRequiredService>().Value; + var parsedConfiguration = + services.BuildServiceProvider().GetRequiredService>().Value; #pragma warning restore ASP0000 - var worker = Assembly.GetExecutingAssembly().DefinedTypes.FirstOrDefault(t => t.Name == parsedConfiguration.Worker) ?? - throw new ArgumentException($"The specified worker could not be recognized, or no worker was set."); - services.AddTransient(typeof(IHostedService), worker); - - services - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration) - .AddModule(configuration); - - services.AddTransient(); - services.AddFluentValidationAutoValidation(config => { config.DisableDataAnnotationsValidation = true; }); - - services.AddCustomIdentity(hostContext.HostingEnvironment); - - services.RegisterIdentityDeleters(); - - services.AddEventBus(parsedConfiguration.Infrastructure.EventBus); - - var devicesConfiguration = new DevicesConfiguration(); - configuration.GetSection("Modules:Devices").Bind(devicesConfiguration); - services.AddPushNotifications(devicesConfiguration.Infrastructure.PushNotifications); - }) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .UseSerilog((context, configuration) => configuration - .ReadFrom.Configuration(context.Configuration, new ConfigurationReaderOptions { SectionName = "Logging" }) - .Enrich.WithDemystifiedStackTraces() - .Enrich.FromLogContext() - .Enrich.WithProperty("service", "jobs.identitydeletion") - .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() }) - ) - ); + var worker = Assembly.GetExecutingAssembly().DefinedTypes.FirstOrDefault(t => t.Name == parsedConfiguration.Worker) ?? + throw new ArgumentException($"The specified worker could not be recognized, or no worker was set."); + services.AddTransient(typeof(IHostedService), worker); + + services + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration); + + services.AddSingleton(); + + services.AddTransient(); + services.AddFluentValidationAutoValidation(config => { config.DisableDataAnnotationsValidation = true; }); + + services.AddCustomIdentity(hostContext.HostingEnvironment); + + services.RegisterIdentityDeleters(); + + services.AddEventBus(parsedConfiguration.Infrastructure.EventBus); + + var devicesConfiguration = new Configuration(); + configuration.GetSection("Modules:Devices").Bind(devicesConfiguration); + services.AddPushNotifications(devicesConfiguration.Infrastructure.PushNotifications); + }) + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .UseSerilog((context, configuration) => configuration + .ReadFrom.Configuration(context.Configuration, new ConfigurationReaderOptions { SectionName = "Logging" }) + .Enrich.WithDemystifiedStackTraces() + .Enrich.FromLogContext() + .Enrich.WithProperty("service", "jobs.identitydeletion") + .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() }) + ) + ); + } } diff --git a/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs b/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs index f90f46d496..433eb3d0c2 100644 --- a/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs +++ b/Jobs/src/Job.IdentityDeletion/Workers/ActualDeletionWorker.cs @@ -12,19 +12,20 @@ namespace Backbone.Job.IdentityDeletion.Workers; public class ActualDeletionWorker : IHostedService { private readonly IHostApplicationLifetime _host; - private readonly IEnumerable _identityDeleters; private readonly IMediator _mediator; private readonly IPushNotificationSender _pushNotificationSender; private readonly ILogger _logger; + private readonly List _identityDeleters; - public ActualDeletionWorker(IHostApplicationLifetime host, + public ActualDeletionWorker( + IHostApplicationLifetime host, IEnumerable identityDeleters, IMediator mediator, IPushNotificationSender pushNotificationSender, ILogger logger) { _host = host; - _identityDeleters = identityDeleters; + _identityDeleters = identityDeleters.ToList(); _mediator = mediator; _pushNotificationSender = pushNotificationSender; _logger = logger; diff --git a/Jobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs b/Jobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs new file mode 100644 index 0000000000..31e8771c12 --- /dev/null +++ b/Jobs/test/Job.IdentityDeletion.Tests.Integration/ActualDeletionWorkerTests.cs @@ -0,0 +1,215 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using Backbone.Modules.Devices.Domain.Aggregates.Tier; +using Backbone.Modules.Devices.Domain.Entities.Identities; +using Backbone.Modules.Devices.Infrastructure.Persistence.Database; +using Backbone.Modules.Messages.Domain.Entities; +using Backbone.Modules.Messages.Infrastructure.Persistence.Database; +using Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates; +using Backbone.Modules.Relationships.Infrastructure.Persistence.Database; +using Backbone.Tooling; +using Backbone.UnitTestTools.Data; +using Backbone.UnitTestTools.Extensions; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Relationship = Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship; + +namespace Backbone.Job.IdentityDeletion.Tests.Integration; + +public class ActualDeletionWorkerTests +{ + private readonly IHost _host; + + public ActualDeletionWorkerTests() + { + var hostBuilder = Program.CreateHostBuilder(["--Worker", "ActualDeletionWorker"]); + + _host = hostBuilder.Build(); + } + + [Fact] + public async Task Logs_that_data_was_deleted() + { + // Arrange + var identity = await SeedDatabaseWithIdentityWithRipeDeletionProcess(); + + // Act + await _host.StartAsync(); + + // Assert + var assertionContext = GetService(); + + var auditLogEntries = await assertionContext.IdentityDeletionProcessAuditLogs.Where(a => a.IdentityAddressHash == Hasher.HashUtf8(identity.Address)).ToListAsync(); + + var auditLogEntriesForDeletedData = auditLogEntries.Where(e => e.MessageKey == MessageKey.DataDeleted).ToList(); + + auditLogEntriesForDeletedData.Should().HaveCount(13); + + auditLogEntriesForDeletedData.Should().AllSatisfy(e => + { + e.AdditionalData.Should().HaveCount(1); + e.AdditionalData!.First().Key.Should().Be("aggregateType"); + }); + + var deletedAggregates = auditLogEntriesForDeletedData.SelectMany(e => e.AdditionalData!.Values).ToList(); + deletedAggregates.Should().Contain("Challenges"); + deletedAggregates.Should().Contain("PnsRegistrations"); + deletedAggregates.Should().Contain("Identities"); + deletedAggregates.Should().Contain("Files"); + deletedAggregates.Should().Contain("Messages"); + deletedAggregates.Should().Contain("QuotaIdentities"); + deletedAggregates.Should().Contain("Relationships"); + deletedAggregates.Should().Contain("RelationshipTemplates"); + deletedAggregates.Should().Contain("RelationshipTemplateAllocations"); + deletedAggregates.Should().Contain("ExternalEvents"); + deletedAggregates.Should().Contain("SyncRuns"); + deletedAggregates.Should().Contain("Datawallets"); + deletedAggregates.Should().Contain("Tokens"); + } + + [Fact] + public async Task Deletes_the_identity() + { + // Arrange + var identity = await SeedDatabaseWithIdentityWithRipeDeletionProcess(); + + // Act + await _host.StartAsync(); + + // Assert + var assertionContext = GetService(); + + var identityAfterAct = await assertionContext.Identities.FirstOrDefaultAsync(i => i.Address == identity.Address); + identityAfterAct.Should().BeNull(); + } + + [Fact] + public async Task Anonymizes_the_identity_in_all_messages_instead_of_deleting_the_message() + { + // Arrange + var identityToBeDeleted = await SeedDatabaseWithIdentityWithRipeDeletionProcess(); + var peer = TestDataGenerator.CreateRandomIdentityAddress(); + + GetService(); + + var sentMessage = await SeedDatabaseWithMessage(from: identityToBeDeleted.Address, to: peer); + var receivedMessage = await SeedDatabaseWithMessage(from: peer, to: identityToBeDeleted.Address); + + // Act + await _host.StartAsync(); + + // Assert + var assertionContext = GetService(); + + var messagesOfIdentityAfterAct = await assertionContext.Messages.Where(Message.HasParticipant(identityToBeDeleted.Address)).ToListAsync(); + messagesOfIdentityAfterAct.Should().BeEmpty(); + + var sentMessageAfterAct = await assertionContext.Messages.FirstOrDefaultAsync(m => m.Id == sentMessage.Id); + sentMessageAfterAct.Should().NotBeNull(); + + var receivedMessageAfterAct = await assertionContext.Messages.FirstOrDefaultAsync(m => m.Id == receivedMessage.Id); + receivedMessageAfterAct.Should().NotBeNull(); + } + + [Fact] + public async Task Deletes_relationships() + { + // Arrange + var identityToBeDeleted = await SeedDatabaseWithIdentityWithRipeDeletionProcess(); + var peer = TestDataGenerator.CreateRandomIdentityAddress(); + + GetService(); + + await SeedDatabaseWithActiveRelationshipBetween(peer, identityToBeDeleted.Address); + + // Act + await _host.StartAsync(); + + // Assert + var assertionContext = GetService(); + + var relationshipsAfterAct = await assertionContext.Relationships.Where(Relationship.HasParticipant(identityToBeDeleted.Address)).ToListAsync(); + relationshipsAfterAct.Should().BeEmpty(); + } + + [Fact] + public async Task Deletes_relationship_templates() + { + // Arrange + var identityToBeDeleted = await SeedDatabaseWithIdentityWithRipeDeletionProcess(); + + GetService(); + + await SeedDatabaseWithRelationshipTemplateOf(identityToBeDeleted.Address); + + // Act + await _host.StartAsync(); + + // Assert + var assertionContext = GetService(); + + var templatesAfterAct = await assertionContext.RelationshipTemplates.Where(rt => rt.CreatedBy == identityToBeDeleted.Address).ToListAsync(); + templatesAfterAct.Should().BeEmpty(); + } + + #region Seeders + + private async Task SeedDatabaseWithMessage(IdentityAddress from, IdentityAddress to) + { + var dbContext = GetService(); + + var recipient = new RecipientInformation(to, []); + var message = new Message(from, DeviceId.New(), [], [], [recipient]); + + await dbContext.SaveEntity(message); + + return message; + } + + private async Task SeedDatabaseWithActiveRelationshipBetween(IdentityAddress from, IdentityAddress to) + { + var dbContext = GetService(); + + var template = new RelationshipTemplate(from, DeviceId.New(), null, null, []); + var relationship = new Relationship(template, to, DeviceId.New(), [], []); + relationship.Accept(from, DeviceId.New(), []); + + await dbContext.SaveEntity(relationship); + } + + private async Task SeedDatabaseWithRelationshipTemplateOf(IdentityAddress owner) + { + var dbContext = GetService(); + + var template = new RelationshipTemplate(owner, DeviceId.New(), null, null, []); + + await dbContext.SaveEntity(template); + } + + private async Task SeedDatabaseWithIdentityWithRipeDeletionProcess() + { + var dbContext = GetService(); + + var identity = new Identity("test", TestDataGenerator.CreateRandomIdentityAddress(), [], TierId.Generate(), 1); + + var device = new Device(identity, CommunicationLanguage.DEFAULT_LANGUAGE); + identity.Devices.Add(device); + + SystemTime.Set(SystemTime.UtcNow.AddMonths(-1)); + identity.StartDeletionProcessAsOwner(device.Id); + SystemTime.Reset(); + + await dbContext.SaveEntity(identity); + + return identity; + } + + #endregion + + private T GetService() where T : notnull + { + return _host.Services.CreateScope().ServiceProvider.GetRequiredService(); + } +} diff --git a/Jobs/test/Job.IdentityDeletion.Tests.Integration/AssemblyInfo.cs b/Jobs/test/Job.IdentityDeletion.Tests.Integration/AssemblyInfo.cs new file mode 100644 index 0000000000..925a8793f8 --- /dev/null +++ b/Jobs/test/Job.IdentityDeletion.Tests.Integration/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: AssemblyTrait("Category", "Integration")] diff --git a/Jobs/test/Job.IdentityDeletion.Tests.Integration/Job.IdentityDeletion.Tests.Integration.csproj b/Jobs/test/Job.IdentityDeletion.Tests.Integration/Job.IdentityDeletion.Tests.Integration.csproj new file mode 100644 index 0000000000..e09b87b4cb --- /dev/null +++ b/Jobs/test/Job.IdentityDeletion.Tests.Integration/Job.IdentityDeletion.Tests.Integration.csproj @@ -0,0 +1,40 @@ + + + + false + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + PreserveNewest + + + + diff --git a/Jobs/test/Job.IdentityDeletion.Tests.Integration/appsettings.override.json b/Jobs/test/Job.IdentityDeletion.Tests.Integration/appsettings.override.json new file mode 100644 index 0000000000..8218c447b5 --- /dev/null +++ b/Jobs/test/Job.IdentityDeletion.Tests.Integration/appsettings.override.json @@ -0,0 +1,107 @@ +{ + "Infrastructure": { + "EventBus": { + "Vendor": "RabbitMQ", // possible values: InMemory, RabbitMQ, GoogleCloud, Azure + "ConnectionInfo": "localhost", + + "RabbitMQUsername": "guest", // only available for RabbitMQ + "RabbitMQPassword": "guest", // only available for RabbitMQ + "ConnectionRetryCount": 5, // only available for RabbitMQ + + "GcpPubSubProjectId": "", // only available for Google Cloud Pub/Sub + "GcpPubSubTopicName": "" // only available for Google Cloud Pub/Sub + } + }, + "Modules": { + "Challenges": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=challenges;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Quotas": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=quotas;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Devices": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=devices;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + }, + "PushNotifications": { + "Providers": { + "Dummy": { + "IsEnabled": true + } + } + } + } + }, + "Files": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=files;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + }, + "BlobStorage": { + "CloudProvider": "Azure", + "ConnectionInfo": "", + "ContainerName": "" + } + } + }, + "Messages": { + "Application": { + "DidDomainName": "localhost" + }, + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=messages;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Relationships": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=relationships;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Synchronization": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=synchronization;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Tokens": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=postgres;Password=admin;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=tokens;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + } + } +} diff --git a/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs b/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs index 41f88f5bfb..057f0730b2 100644 --- a/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs +++ b/Jobs/test/Job.IdentityDeletion.Tests/Tests/ActualDeletionWorkerTests.cs @@ -24,11 +24,9 @@ public async Task Proxies_triggering_the_deletion_processes_to_command_handler() { // Arrange var mockMediator = A.Fake(); - SetupRipeDeletionProcessesCommand(mockMediator); - var identityDeleters = new List(); - var worker = CreateWorker(mockMediator, identityDeleters); + var worker = CreateWorker(mockMediator, []); // Act await worker.StartProcessing(CancellationToken.None); @@ -41,39 +39,38 @@ public async Task Proxies_triggering_the_deletion_processes_to_command_handler() public async Task Calls_Deleters_For_Each_Identity() { // Arrange - var mediator = A.Fake(); + var fakeMediator = A.Fake(); var identityAddress1 = TestDataGenerator.CreateRandomIdentityAddress(); var identityAddress2 = TestDataGenerator.CreateRandomIdentityAddress(); - SetupRipeDeletionProcessesCommand(mediator, identityAddress1, identityAddress2); - var mockIdentityDeleter = A.Fake(); - var identityDeleters = new List([mockIdentityDeleter]); + SetupRipeDeletionProcessesCommand(fakeMediator, identityAddress1, identityAddress2); - var worker = CreateWorker(mediator, identityDeleters); + var mockIdentityDeleter = A.Fake(); + var worker = CreateWorker(fakeMediator, [mockIdentityDeleter]); - A.CallTo(() => mediator.Send(A._, A._)).Returns(new FindRelationshipsOfIdentityResponse(new List())); + A.CallTo(() => fakeMediator.Send(A._, A._)) + .Returns(new FindRelationshipsOfIdentityResponse(new List())); // Act await worker.StartProcessing(CancellationToken.None); // Assert - A.CallTo(() => mockIdentityDeleter.Delete(identityAddress1)).MustHaveHappenedOnceExactly(); A.CallTo(() => mockIdentityDeleter.Delete(identityAddress2)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task Sends_push_notification_for_each_relationship_of_each_identity() + public async Task Sends_push_notification_to_each_deleted_identity() { // Arrange - var mockMediator = A.Fake(); + var fakeMediator = A.Fake(); var identityAddress1 = TestDataGenerator.CreateRandomIdentityAddress(); var identityAddress2 = TestDataGenerator.CreateRandomIdentityAddress(); var identityAddress3 = TestDataGenerator.CreateRandomIdentityAddress(); - SetupRipeDeletionProcessesCommand(mockMediator, identityAddress1, identityAddress2, identityAddress3); - A.CallTo(() => mockMediator.Send(A._, A._)).Returns(new FindRelationshipsOfIdentityResponse(new List())); + SetupRipeDeletionProcessesCommand(fakeMediator, identityAddress1, identityAddress2, identityAddress3); + A.CallTo(() => fakeMediator.Send(A._, A._)).Returns(new FindRelationshipsOfIdentityResponse([])); - var mockPushNotificationSender = A.Dummy(); - var worker = CreateWorker(mockMediator, mockPushNotificationSender); + var mockPushNotificationSender = A.Fake(); + var worker = CreateWorker(fakeMediator, [], mockPushNotificationSender); // Act await worker.StartProcessing(CancellationToken.None); @@ -91,11 +88,6 @@ private void SetupRipeDeletionProcessesCommand(IMediator mediator, params Identi A.CallTo(() => mediator.Send(A._, A._)).Returns(commandResponse); } - private static ActualDeletionWorker CreateWorker(IMediator mediator, IPushNotificationSender pushNotificationSender) - { - return CreateWorker(mediator, null, pushNotificationSender); - } - private static ActualDeletionWorker CreateWorker(IMediator mediator, List? identityDeleters = null, IPushNotificationSender? pushNotificationSender = null) diff --git a/Modules/Challenges/src/Challenges.Application/Identities/IdentityDeleter.cs b/Modules/Challenges/src/Challenges.Application/Identities/IdentityDeleter.cs index 1dc395c7f7..660f7e8bd6 100644 --- a/Modules/Challenges/src/Challenges.Application/Identities/IdentityDeleter.cs +++ b/Modules/Challenges/src/Challenges.Application/Identities/IdentityDeleter.cs @@ -4,17 +4,21 @@ using MediatR; namespace Backbone.Modules.Challenges.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteChallengesOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Challenges"); } } diff --git a/Modules/Challenges/test/Challenges.Application.Tests/Challenges.Application.Tests.csproj b/Modules/Challenges/test/Challenges.Application.Tests/Challenges.Application.Tests.csproj index c6ac290cc9..f255a8fdcc 100644 --- a/Modules/Challenges/test/Challenges.Application.Tests/Challenges.Application.Tests.csproj +++ b/Modules/Challenges/test/Challenges.Application.Tests/Challenges.Application.Tests.csproj @@ -5,13 +5,17 @@ - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/Commands/DeleteIdentityCommandTests/HandlerTests.cs b/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/Commands/DeleteIdentityCommandTests/HandlerTests.cs index d6d217dc19..433849cc7e 100644 --- a/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/Commands/DeleteIdentityCommandTests/HandlerTests.cs +++ b/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/Commands/DeleteIdentityCommandTests/HandlerTests.cs @@ -2,17 +2,19 @@ using Backbone.Modules.Challenges.Application.Challenges.Commands.DeleteChallengesOfIdentity; using Backbone.Modules.Challenges.Application.Infrastructure.Persistence.Repository; using Backbone.Modules.Challenges.Domain.Entities; +using Backbone.UnitTestTools.BaseClasses; using FakeItEasy; using Xunit; namespace Backbone.Modules.Challenges.Application.Tests.Tests.Identities.Commands.DeleteIdentityCommandTests; -public class HandlerTests + +public class HandlerTests : AbstractTestsBase { [Fact] public async Task Handler_calls_deletion_method_on_repository() { // Arrange - var identityAddress = "identity-address"; + const string identityAddress = "identity-address"; var mockChallengesRepository = A.Fake(); var handler = CreateHandler(mockChallengesRepository); @@ -20,7 +22,7 @@ public async Task Handler_calls_deletion_method_on_repository() await handler.Handle(new DeleteChallengesOfIdentityCommand(identityAddress), CancellationToken.None); // Assert - A.CallTo(() => mockChallengesRepository.DeleteChallenges(A>>._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockChallengesRepository.Delete(A>>._, A._)).MustHaveHappenedOnceExactly(); } private static Handler CreateHandler(IChallengesRepository challengesRepository) diff --git a/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/IdentityDeleterTests.cs index 3e6304997f..bacad387a7 100644 --- a/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/IdentityDeleterTests.cs +++ b/Modules/Challenges/test/Challenges.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -1,20 +1,24 @@ -using Backbone.Modules.Challenges.Application.Challenges.Commands.DeleteChallengesOfIdentity; +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Challenges.Application.Challenges.Commands.DeleteChallengesOfIdentity; using Backbone.Modules.Challenges.Application.Identities; -using Backbone.UnitTestTools.Data; +using Backbone.UnitTestTools.BaseClasses; using FakeItEasy; using MediatR; using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; namespace Backbone.Modules.Challenges.Application.Tests.Tests.Identities; -public class IdentityDeleterTests + +public class IdentityDeleterTests : AbstractTestsBase { [Fact] public async Task Deleter_calls_correct_command() { // Arrange var mockMediator = A.Fake(); - var identityAddress = TestDataGenerator.CreateRandomIdentityAddress(); - var deleter = new IdentityDeleter(mockMediator); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); // Act await deleter.Delete(identityAddress); @@ -22,4 +26,20 @@ public async Task Deleter_calls_correct_command() // Assert A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Challenges")).MustHaveHappenedOnceExactly(); + } } diff --git a/Modules/Devices/src/Devices.Application/DTOs/IdentityDeletionProcessDetailsDTO.cs b/Modules/Devices/src/Devices.Application/DTOs/IdentityDeletionProcessDetailsDTO.cs index 76b2f9ea0f..e99e5df897 100644 --- a/Modules/Devices/src/Devices.Application/DTOs/IdentityDeletionProcessDetailsDTO.cs +++ b/Modules/Devices/src/Devices.Application/DTOs/IdentityDeletionProcessDetailsDTO.cs @@ -58,14 +58,16 @@ public IdentityDeletionProcessAuditLogEntryDTO(IdentityDeletionProcessAuditLogEn { Id = entry.Id; CreatedAt = entry.CreatedAt; - MessageKey = entry.MessageKey; OldStatus = entry.OldStatus; NewStatus = entry.NewStatus; + MessageKey = entry.MessageKey; + AdditionalData = entry.AdditionalData ?? []; } public string Id { get; set; } public DateTime CreatedAt { get; set; } - public MessageKey MessageKey { get; set; } public DeletionProcessStatus? OldStatus { get; set; } public DeletionProcessStatus NewStatus { get; set; } + public Dictionary AdditionalData { get; set; } + public MessageKey MessageKey { get; set; } } diff --git a/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/Handler.cs b/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/Handler.cs new file mode 100644 index 0000000000..28732d1e7e --- /dev/null +++ b/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/Handler.cs @@ -0,0 +1,20 @@ +using Backbone.Modules.Devices.Application.Infrastructure.Persistence.Repository; +using Backbone.Modules.Devices.Domain.Entities.Identities; +using MediatR; + +namespace Backbone.Modules.Devices.Application.Identities.Commands.LogDeletionProcess; + +public class Handler : IRequestHandler +{ + private readonly IIdentitiesRepository _identitiesRepository; + + public Handler(IIdentitiesRepository identitiesRepository) + { + _identitiesRepository = identitiesRepository; + } + + public async Task Handle(LogDeletionProcessCommand request, CancellationToken cancellationToken) + { + await _identitiesRepository.AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry.DataDeleted(request.IdentityAddress, request.AggregateType.ToString())); + } +} diff --git a/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/LogDeletionProcessCommand.cs b/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/LogDeletionProcessCommand.cs new file mode 100644 index 0000000000..7b6f039405 --- /dev/null +++ b/Modules/Devices/src/Devices.Application/Identities/Commands/LogDeletionProcess/LogDeletionProcessCommand.cs @@ -0,0 +1,16 @@ +using Backbone.DevelopmentKit.Identity.ValueObjects; +using MediatR; + +namespace Backbone.Modules.Devices.Application.Identities.Commands.LogDeletionProcess; + +public class LogDeletionProcessCommand : IRequest +{ + public LogDeletionProcessCommand(IdentityAddress identityAddress, string aggregateType) + { + IdentityAddress = identityAddress; + AggregateType = aggregateType; + } + + public string IdentityAddress { get; set; } + public string AggregateType { get; set; } +} diff --git a/Modules/Devices/src/Devices.Application/Identities/IdentityDeleter.cs b/Modules/Devices/src/Devices.Application/Identities/IdentityDeleter.cs index 4223d17381..02376dba73 100644 --- a/Modules/Devices/src/Devices.Application/Identities/IdentityDeleter.cs +++ b/Modules/Devices/src/Devices.Application/Identities/IdentityDeleter.cs @@ -5,18 +5,23 @@ using MediatR; namespace Backbone.Modules.Devices.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeletePnsRegistrationsOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "PnsRegistrations"); await _mediator.Send(new DeleteIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Identities"); } } diff --git a/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs b/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs index 922a03c60c..ad72c45047 100644 --- a/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs +++ b/Modules/Devices/src/Devices.Application/Infrastructure/Persistence/Repository/IIdentitiesRepository.cs @@ -9,6 +9,7 @@ namespace Backbone.Modules.Devices.Application.Infrastructure.Persistence.Reposi public interface IIdentitiesRepository { #region Identities + Task Update(Identity identity, CancellationToken cancellationToken); Task FindByAddress(IdentityAddress address, CancellationToken cancellationToken, bool track = false); Task Exists(IdentityAddress address, CancellationToken cancellationToken); @@ -16,19 +17,27 @@ public interface IIdentitiesRepository Task CountByClientId(string clientId, CancellationToken cancellationToken); Task> Find(Expression> filter, CancellationToken cancellationToken, bool track = false); Task Delete(Expression> filter, CancellationToken cancellationToken); + #endregion #region Users + Task AddUser(ApplicationUser user, string password); + #endregion #region Devices + Task> FindAllDevicesOfIdentity(IdentityAddress identity, IEnumerable ids, PaginationFilter paginationFilter, CancellationToken cancellationToken); Task GetDeviceById(DeviceId deviceId, CancellationToken cancellationToken, bool track = false); Task Update(Device device, CancellationToken cancellationToken); + #endregion #region Deletion Process Audit Logs + Task> GetIdentityDeletionProcessAuditLogsByAddress(byte[] identityAddressHash, CancellationToken cancellationToken); + Task AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry auditLogEntry); + #endregion } diff --git a/Modules/Devices/src/Devices.Application/PushNotifications/Commands/DeletePnsRegistrationsOfIdentity/Handler.cs b/Modules/Devices/src/Devices.Application/PushNotifications/Commands/DeletePnsRegistrationsOfIdentity/Handler.cs index cbef231839..3f0e65a115 100644 --- a/Modules/Devices/src/Devices.Application/PushNotifications/Commands/DeletePnsRegistrationsOfIdentity/Handler.cs +++ b/Modules/Devices/src/Devices.Application/PushNotifications/Commands/DeletePnsRegistrationsOfIdentity/Handler.cs @@ -3,6 +3,7 @@ using MediatR; namespace Backbone.Modules.Devices.Application.PushNotifications.Commands.DeletePnsRegistrationsOfIdentity; + public class Handler : IRequestHandler { private readonly IPnsRegistrationsRepository _pnsRegistrationRepository; diff --git a/Modules/Devices/src/Devices.Domain/Entities/Identities/IdentityDeletionProcessAuditLogEntry.cs b/Modules/Devices/src/Devices.Domain/Entities/Identities/IdentityDeletionProcessAuditLogEntry.cs index e83a872eb0..23b3095389 100644 --- a/Modules/Devices/src/Devices.Domain/Entities/Identities/IdentityDeletionProcessAuditLogEntry.cs +++ b/Modules/Devices/src/Devices.Domain/Entities/Identities/IdentityDeletionProcessAuditLogEntry.cs @@ -15,8 +15,8 @@ private IdentityDeletionProcessAuditLogEntry() IdentityAddressHash = null!; } - private IdentityDeletionProcessAuditLogEntry(IdentityDeletionProcessId processId, MessageKey messageKey, byte[] identityAddressHash, byte[]? deviceIdHash, DeletionProcessStatus? oldStatus, - DeletionProcessStatus newStatus) + private IdentityDeletionProcessAuditLogEntry(IdentityDeletionProcessId? processId, MessageKey messageKey, byte[] identityAddressHash, byte[]? deviceIdHash, DeletionProcessStatus? oldStatus, + DeletionProcessStatus newStatus, Dictionary? additionalData = null) { Id = IdentityDeletionProcessAuditLogEntryId.Generate(); ProcessId = processId; @@ -26,16 +26,18 @@ private IdentityDeletionProcessAuditLogEntry(IdentityDeletionProcessId processId DeviceIdHash = deviceIdHash; OldStatus = oldStatus; NewStatus = newStatus; + AdditionalData = additionalData; } public IdentityDeletionProcessAuditLogEntryId Id { get; } - public IdentityDeletionProcessId ProcessId { get; } + public IdentityDeletionProcessId? ProcessId { get; } public DateTime CreatedAt { get; } public MessageKey MessageKey { get; } public byte[] IdentityAddressHash { get; } public byte[]? DeviceIdHash { get; } public DeletionProcessStatus? OldStatus { get; } public DeletionProcessStatus NewStatus { get; } + public Dictionary? AdditionalData { get; } public static IdentityDeletionProcessAuditLogEntry ProcessStartedByOwner(IdentityDeletionProcessId processId, IdentityAddress identityAddress, DeviceId deviceId) { @@ -191,6 +193,22 @@ public static IdentityDeletionProcessAuditLogEntry GracePeriodReminder3Sent(Iden DeletionProcessStatus.Approved ); } + + public static IdentityDeletionProcessAuditLogEntry DataDeleted(IdentityAddress identityAddress, string aggregateType) + { + return new IdentityDeletionProcessAuditLogEntry( + null, + MessageKey.DataDeleted, + Hasher.HashUtf8(identityAddress.Value), + null, + DeletionProcessStatus.Deleting, + DeletionProcessStatus.Deleting, + new Dictionary + { + { "aggregateType", aggregateType } + } + ); + } } public enum MessageKey @@ -207,5 +225,6 @@ public enum MessageKey ApprovalReminder3Sent = 10, GracePeriodReminder1Sent = 11, GracePeriodReminder2Sent = 12, - GracePeriodReminder3Sent = 13 + GracePeriodReminder3Sent = 13, + DataDeleted = 14 } diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs new file mode 100644 index 0000000000..285b1f8a59 --- /dev/null +++ b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs @@ -0,0 +1,915 @@ +// +using System; +using Backbone.Modules.Devices.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.Devices.Infrastructure.Database.Postgres.Migrations +{ + [DbContext(typeof(DevicesDbContext))] + [Migration("20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry")] + partial class AddAdditionalDataToIdentityDeletionProcessAuditLogEntry + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Devices") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Aggregates.PushNotifications.PnsRegistration", b => + { + b.Property("DeviceId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("AppId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DevicePushIdentifier") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Environment") + .HasColumnType("integer"); + + b.Property("Handle") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(true) + .HasColumnType("character varying(200)") + .IsFixedLength(false); + + b.Property("IdentityAddress") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("DeviceId"); + + b.ToTable("PnsRegistrations", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Aggregates.Tier.Tier", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CanBeManuallyAssigned") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CanBeUsedAsDefaultForClient") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .IsUnicode(true) + .HasColumnType("character varying(30)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Tiers", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Challenge", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Challenges", "Challenges", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("UserName") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CommunicationLanguage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(2) + .IsUnicode(false) + .HasColumnType("character(2)") + .HasDefaultValue("en") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("IdentityAddress") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.HasIndex("IdentityAddress"); + + b.ToTable("Devices", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", b => + { + b.Property("Address") + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletionGracePeriodEndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityVersion") + .HasColumnType("smallint"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TierId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("TierIdBeforeDeletion") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Address"); + + b.HasIndex("ClientId"); + + b.HasIndex("TierId"); + + b.ToTable("Identities", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("ApprovalReminder1SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovalReminder2SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovalReminder3SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletionStartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GracePeriodEndsAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GracePeriodReminder1SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GracePeriodReminder2SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GracePeriodReminder3SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityAddress") + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("RejectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RejectedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IdentityAddress"); + + b.ToTable("IdentityDeletionProcesses", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcessAuditLogEntry", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("AdditionalData") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceIdHash") + .HasColumnType("bytea"); + + b.Property("IdentityAddressHash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("IdentityDeletionProcessId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("MessageKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("NewStatus") + .HasColumnType("integer"); + + b.Property("OldStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IdentityDeletionProcessId"); + + b.ToTable("IdentityDeletionProcessAuditLog", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultTier") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("MaxIdentities") + .HasColumnType("integer"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("DefaultTier"); + + b.ToTable("OpenIddictApplications", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Device", "Device") + .WithOne("User") + .HasForeignKey("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", "DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", "Identity") + .WithMany("Devices") + .HasForeignKey("IdentityAddress") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", null) + .WithMany("DeletionProcesses") + .HasForeignKey("IdentityAddress") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcessAuditLogEntry", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", null) + .WithMany("AuditLog") + .HasForeignKey("IdentityDeletionProcessId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Aggregates.Tier.Tier", null) + .WithMany() + .HasForeignKey("DefaultTier") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.Navigation("User") + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", b => + { + b.Navigation("DeletionProcesses"); + + b.Navigation("Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.Navigation("AuditLog"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs new file mode 100644 index 0000000000..afebb4a4d7 --- /dev/null +++ b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/20240708114348_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Devices.Infrastructure.Database.Postgres.Migrations +{ + /// + public partial class AddAdditionalDataToIdentityDeletionProcessAuditLogEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AdditionalData", + schema: "Devices", + table: "IdentityDeletionProcessAuditLog", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AdditionalData", + schema: "Devices", + table: "IdentityDeletionProcessAuditLog"); + } + } +} diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/DevicesDbContextModelSnapshot.cs b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/DevicesDbContextModelSnapshot.cs index 87ea0b065b..56570b5c11 100644 --- a/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/DevicesDbContextModelSnapshot.cs +++ b/Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Migrations/DevicesDbContextModelSnapshot.cs @@ -364,6 +364,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character(20)") .IsFixedLength(); + b.Property("AdditionalData") + .HasColumnType("text"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs new file mode 100644 index 0000000000..d50cf0b4f6 --- /dev/null +++ b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.Designer.cs @@ -0,0 +1,920 @@ +// +using System; +using Backbone.Modules.Devices.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.Devices.Infrastructure.Database.SqlServer.Migrations +{ + [DbContext(typeof(DevicesDbContext))] + [Migration("20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry")] + partial class AddAdditionalDataToIdentityDeletionProcessAuditLogEntry + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Devices") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Aggregates.PushNotifications.PnsRegistration", b => + { + b.Property("DeviceId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("AppId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DevicePushIdentifier") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Environment") + .HasColumnType("int"); + + b.Property("Handle") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(true) + .HasColumnType("nvarchar(200)") + .IsFixedLength(false); + + b.Property("IdentityAddress") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("DeviceId"); + + b.ToTable("PnsRegistrations", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Aggregates.Tier.Tier", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CanBeManuallyAssigned") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("CanBeUsedAsDefaultForClient") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .IsUnicode(true) + .HasColumnType("nvarchar(30)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Tiers", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Challenge", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Challenges", "Challenges", t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("LastLoginAt") + .HasColumnType("datetime2"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("UserName") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CommunicationLanguage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(2) + .IsUnicode(false) + .HasColumnType("char(2)") + .HasDefaultValue("en") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("IdentityAddress") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.HasKey("Id"); + + b.HasIndex("IdentityAddress"); + + b.ToTable("Devices", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", b => + { + b.Property("Address") + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeletionGracePeriodEndsAt") + .HasColumnType("datetime2"); + + b.Property("IdentityVersion") + .HasColumnType("tinyint"); + + b.Property("PublicKey") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TierId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("TierIdBeforeDeletion") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Address"); + + b.HasIndex("ClientId"); + + b.HasIndex("TierId"); + + b.ToTable("Identities", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("ApprovalReminder1SentAt") + .HasColumnType("datetime2"); + + b.Property("ApprovalReminder2SentAt") + .HasColumnType("datetime2"); + + b.Property("ApprovalReminder3SentAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CancelledAt") + .HasColumnType("datetime2"); + + b.Property("CancelledByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeletionStartedAt") + .HasColumnType("datetime2"); + + b.Property("GracePeriodEndsAt") + .HasColumnType("datetime2"); + + b.Property("GracePeriodReminder1SentAt") + .HasColumnType("datetime2"); + + b.Property("GracePeriodReminder2SentAt") + .HasColumnType("datetime2"); + + b.Property("GracePeriodReminder3SentAt") + .HasColumnType("datetime2"); + + b.Property("IdentityAddress") + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("RejectedAt") + .HasColumnType("datetime2"); + + b.Property("RejectedByDevice") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IdentityAddress"); + + b.ToTable("IdentityDeletionProcesses", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcessAuditLogEntry", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("AdditionalData") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceIdHash") + .HasColumnType("varbinary(max)"); + + b.Property("IdentityAddressHash") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("IdentityDeletionProcessId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("MessageKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IdentityDeletionProcessId"); + + b.ToTable("IdentityDeletionProcessAuditLog", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ClientSecret") + .HasColumnType("nvarchar(max)"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DefaultTier") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("JsonWebKeySet") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxIdentities") + .HasColumnType("int"); + + b.Property("Permissions") + .HasColumnType("nvarchar(max)"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedirectUris") + .HasColumnType("nvarchar(max)"); + + b.Property("Requirements") + .HasColumnType("nvarchar(max)"); + + b.Property("Settings") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique() + .HasFilter("[ClientId] IS NOT NULL"); + + b.HasIndex("DefaultTier"); + + b.ToTable("OpenIddictApplications", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Scopes") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Descriptions") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayNames") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Resources") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("OpenIddictScopes", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("AuthorizationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ExpirationDate") + .HasColumnType("datetime2"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedemptionDate") + .HasColumnType("datetime2"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique() + .HasFilter("[ReferenceId] IS NOT NULL"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", "Devices"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", "Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Device", "Device") + .WithOne("User") + .HasForeignKey("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", "DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", "Identity") + .WithMany("Devices") + .HasForeignKey("IdentityAddress") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Identity"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", null) + .WithMany("DeletionProcesses") + .HasForeignKey("IdentityAddress") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcessAuditLogEntry", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", null) + .WithMany("AuditLog") + .HasForeignKey("IdentityDeletionProcessId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Aggregates.Tier.Tier", null) + .WithMany() + .HasForeignKey("DefaultTier") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Backbone.Modules.Devices.Domain.Entities.Identities.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Device", b => + { + b.Navigation("User") + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.Identity", b => + { + b.Navigation("DeletionProcesses"); + + b.Navigation("Devices"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Domain.Entities.Identities.IdentityDeletionProcess", b => + { + b.Navigation("AuditLog"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Backbone.Modules.Devices.Infrastructure.OpenIddict.CustomOpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs new file mode 100644 index 0000000000..c6c93acffe --- /dev/null +++ b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/20240708114346_AddAdditionalDataToIdentityDeletionProcessAuditLogEntry.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Devices.Infrastructure.Database.SqlServer.Migrations +{ + /// + public partial class AddAdditionalDataToIdentityDeletionProcessAuditLogEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AdditionalData", + schema: "Devices", + table: "IdentityDeletionProcessAuditLog", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AdditionalData", + schema: "Devices", + table: "IdentityDeletionProcessAuditLog"); + } + } +} diff --git a/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/DevicesDbContextModelSnapshot.cs b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/DevicesDbContextModelSnapshot.cs index 15a262b5e3..2f11a773e1 100644 --- a/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/DevicesDbContextModelSnapshot.cs +++ b/Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Migrations/DevicesDbContextModelSnapshot.cs @@ -365,6 +365,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("char(20)") .IsFixedLength(); + b.Property("AdditionalData") + .HasColumnType("nvarchar(max)"); + b.Property("CreatedAt") .HasColumnType("datetime2"); diff --git a/Modules/Devices/src/Devices.Infrastructure/Persistence/Database/EntityConfigurations/IdentityDeletionProcessEntityTypeConfiguration.cs b/Modules/Devices/src/Devices.Infrastructure/Persistence/Database/EntityConfigurations/IdentityDeletionProcessEntityTypeConfiguration.cs index 796bb3c047..d02dc04b32 100644 --- a/Modules/Devices/src/Devices.Infrastructure/Persistence/Database/EntityConfigurations/IdentityDeletionProcessEntityTypeConfiguration.cs +++ b/Modules/Devices/src/Devices.Infrastructure/Persistence/Database/EntityConfigurations/IdentityDeletionProcessEntityTypeConfiguration.cs @@ -1,4 +1,5 @@ -using Backbone.BuildingBlocks.Infrastructure.Persistence.Database.EntityTypeConfigurations; +using System.Text.Json; +using Backbone.BuildingBlocks.Infrastructure.Persistence.Database.EntityTypeConfigurations; using Backbone.Modules.Devices.Domain.Entities.Identities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -38,5 +39,9 @@ public override void Configure(EntityTypeBuilder x.NewStatus); builder.Property(x => x.OldStatus); builder.Property(x => x.MessageKey).HasConversion(); + builder.Property(x => x.AdditionalData).HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions.Default) + ); } } diff --git a/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs b/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs index 334ca0d7a3..fbef1f4e03 100644 --- a/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs +++ b/Modules/Devices/src/Devices.Infrastructure/Persistence/Repository/IdentitiesRepository.cs @@ -22,6 +22,7 @@ public class IdentitiesRepository : IIdentitiesRepository private readonly DbSet _devices; private readonly IQueryable _readonlyDevices; private readonly UserManager _userManager; + private readonly DbSet _identityDeletionProcessAuditLogs; private readonly IQueryable _readonlyIdentityDeletionProcessAuditLogs; public IdentitiesRepository(DevicesDbContext dbContext, UserManager userManager) @@ -31,6 +32,7 @@ public IdentitiesRepository(DevicesDbContext dbContext, UserManager> filter, CancellationTo { await _identities.Where(filter).ExecuteDeleteAsync(cancellationToken); } + + public async Task AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry auditLogEntry) + { + _identityDeletionProcessAuditLogs.Add(auditLogEntry); + await _dbContext.SaveChangesAsync(); + } } diff --git a/Modules/Devices/test/Devices.Application.Tests/ApplicationDbContextExtensions.cs b/Modules/Devices/test/Devices.Application.Tests/ApplicationDbContextExtensions.cs deleted file mode 100644 index abfbd2049e..0000000000 --- a/Modules/Devices/test/Devices.Application.Tests/ApplicationDbContextExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Backbone.Modules.Devices.Infrastructure.Persistence.Database; - -namespace Backbone.Modules.Devices.Application.Tests; - -public static class ApplicationDbContextExtensions -{ - public static TEntity[] SaveEntities(this DevicesDbContext dbContext, params TEntity[] entities) where TEntity : class - { - dbContext.Set().AddRange(entities); - dbContext.SaveChanges(); - return entities; - } - - public static TEntity SaveEntity(this DevicesDbContext dbContext, TEntity entity) where TEntity : class - { - dbContext.Set().Add(entity); - dbContext.SaveChanges(); - return entity; - } - - public static void RemoveEntity(this DevicesDbContext dbContext, TEntity entity) where TEntity : class - { - dbContext.Set().Remove(entity); - dbContext.SaveChanges(); - } -} diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/IdentityDeleterTests.cs index bdbec7a917..eba5bac108 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/IdentityDeleterTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -1,4 +1,5 @@ -using Backbone.Modules.Devices.Application.Identities; +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Devices.Application.Identities; using Backbone.Modules.Devices.Application.Identities.Commands.DeleteIdentity; using Backbone.Modules.Devices.Application.PushNotifications.Commands.DeletePnsRegistrationsOfIdentity; using Backbone.UnitTestTools.BaseClasses; @@ -8,6 +9,7 @@ using static Backbone.UnitTestTools.Data.TestDataGenerator; namespace Backbone.Modules.Devices.Application.Tests.Tests.Identities; + public class IdentityDeleterTests : AbstractTestsBase { [Fact] @@ -15,8 +17,9 @@ public async Task Deleter_calls_correct_command() { // Arrange var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); var identityAddress = CreateRandomIdentityAddress(); - var deleter = new IdentityDeleter(mockMediator); // Act await deleter.Delete(identityAddress); @@ -25,4 +28,21 @@ public async Task Deleter_calls_correct_command() A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Identities")).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "PnsRegistrations")).MustHaveHappenedOnceExactly(); + } } diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/FindDeletionProcessAuditLogsByAddressStubRepository.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/FindDeletionProcessAuditLogsByAddressStubRepository.cs index c17377124d..85d03f57a6 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/FindDeletionProcessAuditLogsByAddressStubRepository.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/FindDeletionProcessAuditLogsByAddressStubRepository.cs @@ -61,6 +61,11 @@ public Task> GetIdentityDeleti return Task.FromResult(_identityDeletionProcessAuditLogs); } + public Task AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry auditLogEntry) + { + throw new NotSupportedException(); + } + public Task Update(Identity identity, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/HandlerTests.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/HandlerTests.cs index a602cc589e..80f4129817 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/HandlerTests.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetDeletionProcessesAuditLogs/HandlerTests.cs @@ -3,6 +3,7 @@ using Backbone.Modules.Devices.Infrastructure.Persistence.Database; using Backbone.Modules.Devices.Infrastructure.Persistence.Repository; using Backbone.UnitTestTools.BaseClasses; +using Backbone.UnitTestTools.Extensions; using Backbone.UnitTestTools.TestDoubles.Fakes; using FakeItEasy; using FluentAssertions; @@ -31,8 +32,8 @@ public async Task Gets_successfully() var identity = TestDataGenerator.CreateIdentityWithOneDevice(); TestDataGenerator.CreateCancelledDeletionProcessFor(identity); - _arrangeDbContext.SaveEntity(identity); - _arrangeDbContext.RemoveEntity(identity); + await _arrangeDbContext.SaveEntity(identity); + await _arrangeDbContext.RemoveEntity(identity); var handler = CreateHandler(_actDbContext); @@ -50,7 +51,7 @@ public async Task Returns_empty_list_for_non_existent_address() var identity = TestDataGenerator.CreateIdentityWithOneDevice(); TestDataGenerator.CreateCancelledDeletionProcessFor(identity); - _arrangeDbContext.SaveEntity(identity); + await _arrangeDbContext.SaveEntity(identity); var handler = CreateHandler(_actDbContext); @@ -70,8 +71,8 @@ public async Task Gets_for_multiple_deletion_processes() TestDataGenerator.CreateRejectedDeletionProcessFor(identity, identity.Devices.First().Id); TestDataGenerator.CreateApprovedDeletionProcessFor(identity, identity.Devices.First().Id); - _arrangeDbContext.SaveEntity(identity); - _arrangeDbContext.RemoveEntity(identity); + await _arrangeDbContext.SaveEntity(identity); + await _arrangeDbContext.RemoveEntity(identity); var handler = CreateHandler(_actDbContext); @@ -89,8 +90,8 @@ public async Task Gets_for_deletion_process_in_status_deleting() var identity = TestDataGenerator.CreateIdentityWithOneDevice(); TestDataGenerator.CreateDeletingDeletionProcessFor(identity, identity.Devices.First().Id); - _arrangeDbContext.SaveEntity(identity); - _arrangeDbContext.RemoveEntity(identity); + await _arrangeDbContext.SaveEntity(identity); + await _arrangeDbContext.RemoveEntity(identity); var handler = CreateHandler(_actDbContext); @@ -108,8 +109,8 @@ public async Task Gets_for_deleted_identity() var identity = TestDataGenerator.CreateIdentityWithOneDevice(); TestDataGenerator.CreateDeletingDeletionProcessFor(identity, identity.Devices.First().Id); - _arrangeDbContext.SaveEntity(identity); - _arrangeDbContext.RemoveEntity(identity); + await _arrangeDbContext.SaveEntity(identity); + await _arrangeDbContext.RemoveEntity(identity); var handler = CreateHandler(_actDbContext); diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs index b172969c4d..eb7dd14298 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/GetIdentity/FindByAddressStubRepository.cs @@ -56,6 +56,11 @@ public Task> GetIdentityDeleti throw new NotSupportedException(); } + public Task AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry auditLogEntry) + { + throw new NotSupportedException(); + } + public Task Update(Identity identity, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs index 72ed9d572b..f5920502a4 100644 --- a/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs +++ b/Modules/Devices/test/Devices.Application.Tests/Tests/Identities/Queries/ListIdentities/FindAllStubRepository.cs @@ -56,6 +56,11 @@ public Task> GetIdentityDeleti throw new NotSupportedException(); } + public Task AddDeletionProcessAuditLogEntry(IdentityDeletionProcessAuditLogEntry auditLogEntry) + { + throw new NotSupportedException(); + } + public Task Update(Identity identity, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/Modules/Files/src/Files.Application/Identities/IdentityDeleter.cs b/Modules/Files/src/Files.Application/Identities/IdentityDeleter.cs index 6c6cc3e98b..6929223b81 100644 --- a/Modules/Files/src/Files.Application/Identities/IdentityDeleter.cs +++ b/Modules/Files/src/Files.Application/Identities/IdentityDeleter.cs @@ -4,17 +4,21 @@ using MediatR; namespace Backbone.Modules.Files.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteFilesOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Files"); } } diff --git a/Modules/Files/test/Files.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Files/test/Files.Application.Tests/Tests/Identities/IdentityDeleterTests.cs new file mode 100644 index 0000000000..4ba7a63f3e --- /dev/null +++ b/Modules/Files/test/Files.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -0,0 +1,45 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Files.Application.Identities; +using Backbone.Modules.Files.Application.Identities.Commands.DeleteFilesOfIdentity; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using MediatR; +using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; + +namespace Backbone.Modules.Files.Application.Tests.Tests.Identities; + +public class IdentityDeleterTests : AbstractTestsBase +{ + [Fact] + public async Task Deleter_calls_correct_command() + { + // Arrange + var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Files")).MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Messages/src/Messages.Application/Identities/IdentityDeleter.cs b/Modules/Messages/src/Messages.Application/Identities/IdentityDeleter.cs index 3242c931c6..02539876cb 100644 --- a/Modules/Messages/src/Messages.Application/Identities/IdentityDeleter.cs +++ b/Modules/Messages/src/Messages.Application/Identities/IdentityDeleter.cs @@ -4,17 +4,21 @@ using MediatR; namespace Backbone.Modules.Messages.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new AnonymizeMessagesOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Messages"); } } diff --git a/Modules/Messages/src/Messages.Application/Messages/Commands/AnonymizeMessagesOfIdentity/Handler.cs b/Modules/Messages/src/Messages.Application/Messages/Commands/AnonymizeMessagesOfIdentity/Handler.cs index 85c3c45cc2..1bcf6ecde9 100644 --- a/Modules/Messages/src/Messages.Application/Messages/Commands/AnonymizeMessagesOfIdentity/Handler.cs +++ b/Modules/Messages/src/Messages.Application/Messages/Commands/AnonymizeMessagesOfIdentity/Handler.cs @@ -21,7 +21,7 @@ public Handler(IMessagesRepository messagesRepository, IOptions> WasCreatedBy(IdentityAddress identityAddress) + public static Expression> HasParticipant(IdentityAddress identityAddress) { - return i => i.CreatedBy == identityAddress.ToString(); + return i => i.CreatedBy == identityAddress || i.Recipients.Any(r => r.Address == identityAddress); } public static Expression> WasExchangedBetween(IdentityAddress identityAddress1, IdentityAddress identityAddress2) diff --git a/Modules/Messages/test/Messages.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Messages/test/Messages.Application.Tests/Tests/Identities/IdentityDeleterTests.cs new file mode 100644 index 0000000000..00124ab1a6 --- /dev/null +++ b/Modules/Messages/test/Messages.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -0,0 +1,45 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Messages.Application.Identities; +using Backbone.Modules.Messages.Application.Messages.Commands.AnonymizeMessagesOfIdentity; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using MediatR; +using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; + +namespace Backbone.Modules.Messages.Application.Tests.Tests.Identities; + +public class IdentityDeleterTests : AbstractTestsBase +{ + [Fact] + public async Task Deleter_calls_correct_command() + { + // Arrange + var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Messages")).MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Quotas/src/Quotas.Application/Identities/IdentityDeleter.cs b/Modules/Quotas/src/Quotas.Application/Identities/IdentityDeleter.cs index b63308cc3d..e8fdd6c87d 100644 --- a/Modules/Quotas/src/Quotas.Application/Identities/IdentityDeleter.cs +++ b/Modules/Quotas/src/Quotas.Application/Identities/IdentityDeleter.cs @@ -4,17 +4,21 @@ using MediatR; namespace Backbone.Modules.Quotas.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "QuotaIdentities"); } } diff --git a/Modules/Quotas/test/Quotas.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Quotas/test/Quotas.Application.Tests/Tests/Identities/IdentityDeleterTests.cs new file mode 100644 index 0000000000..5bb226742f --- /dev/null +++ b/Modules/Quotas/test/Quotas.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -0,0 +1,45 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Quotas.Application.Identities; +using Backbone.Modules.Quotas.Application.Identities.Commands.DeleteIdentity; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using MediatR; +using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; + +namespace Backbone.Modules.Quotas.Application.Tests.Tests.Identities; + +public class IdentityDeleterTests : AbstractTestsBase +{ + [Fact] + public async Task Deleter_calls_correct_command() + { + // Arrange + var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "QuotaIdentities")).MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.cs b/Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.cs index d7b07c0f8b..404b64fff9 100644 --- a/Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.cs +++ b/Modules/Relationships/src/Relationships.Application/Identities/IdentityDeleter.cs @@ -10,16 +10,21 @@ namespace Backbone.Modules.Relationships.Application.Identities; public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteRelationshipsOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Relationships"); await _mediator.Send(new DeleteRelationshipTemplatesOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplates"); await _mediator.Send(new AnonymizeRelationshipTemplateAllocationsAllocatedByIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplateAllocations"); } } diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs new file mode 100644 index 0000000000..2423c87518 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs @@ -0,0 +1,257 @@ +// +using System; +using Backbone.Modules.Relationships.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.Relationships.Infrastructure.Database.Postgres.Migrations +{ + [DbContext(typeof(RelationshipsDbContext))] + [Migration("20240703100000_ConfigureRelationshipAuditLogDeleteBehavior")] + partial class ConfigureRelationshipAuditLogDeleteBehavior + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Relationships") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Content") + .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.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MaxNumberOfAllocations") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("RelationshipTemplates", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplateAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AllocatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("AllocatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("RelationshipTemplateId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("RelationshipTemplateId", "AllocatedBy"); + + b.ToTable("RelationshipTemplateAllocations", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationContent") + .HasColumnType("bytea"); + + b.Property("CreationResponseContent") + .HasColumnType("bytea"); + + b.Property("From") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("FromHasDecomposed") + .HasColumnType("boolean"); + + b.Property("RelationshipTemplateId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("To") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("character varying(80)") + .IsFixedLength(false); + + b.Property("ToHasDecomposed") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("From"); + + b.HasIndex("RelationshipTemplateId"); + + b.HasIndex("To"); + + b.ToTable("Relationships", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.RelationshipAuditLogEntry", 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("NewStatus") + .HasColumnType("integer"); + + b.Property("OldStatus") + .HasColumnType("integer"); + + b.Property("Reason") + .HasColumnType("integer"); + + b.Property("RelationshipId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("character(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("RelationshipId"); + + b.ToTable("RelationshipAuditLog", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplateAllocation", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", null) + .WithMany("Allocations") + .HasForeignKey("RelationshipTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", "RelationshipTemplate") + .WithMany("Relationships") + .HasForeignKey("RelationshipTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("RelationshipTemplate"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.RelationshipAuditLogEntry", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", null) + .WithMany("AuditLog") + .HasForeignKey("RelationshipId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => + { + b.Navigation("Allocations"); + + b.Navigation("Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.Navigation("AuditLog"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.cs new file mode 100644 index 0000000000..08294f924a --- /dev/null +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/20240703100000_ConfigureRelationshipAuditLogDeleteBehavior.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Relationships.Infrastructure.Database.Postgres.Migrations +{ + /// + public partial class ConfigureRelationshipAuditLogDeleteBehavior : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog"); + + migrationBuilder.AddForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog", + column: "RelationshipId", + principalSchema: "Relationships", + principalTable: "Relationships", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog"); + + migrationBuilder.AddForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog", + column: "RelationshipId", + principalSchema: "Relationships", + principalTable: "Relationships", + principalColumn: "Id"); + } + } +} diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/RelationshipsDbContextModelSnapshot.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/RelationshipsDbContextModelSnapshot.cs index 08ee62b13e..741a9e8695 100644 --- a/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/RelationshipsDbContextModelSnapshot.cs +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Migrations/RelationshipsDbContextModelSnapshot.cs @@ -233,7 +233,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", null) .WithMany("AuditLog") - .HasForeignKey("RelationshipId"); + .HasForeignKey("RelationshipId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs new file mode 100644 index 0000000000..056f0fe387 --- /dev/null +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.Designer.cs @@ -0,0 +1,257 @@ +// +using System; +using Backbone.Modules.Relationships.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.Relationships.Infrastructure.Database.SqlServer.Migrations +{ + [DbContext(typeof(RelationshipsDbContext))] + [Migration("20240703095959_ConfigureRelationshipAuditLogDeleteBehavior")] + partial class ConfigureRelationshipAuditLogDeleteBehavior + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Relationships") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Content") + .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.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("MaxNumberOfAllocations") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RelationshipTemplates", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplateAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AllocatedAt") + .HasColumnType("datetime2"); + + b.Property("AllocatedBy") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("AllocatedByDevice") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("RelationshipTemplateId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("RelationshipTemplateId", "AllocatedBy"); + + b.ToTable("RelationshipTemplateAllocations", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.Property("Id") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreationContent") + .HasColumnType("varbinary(max)"); + + b.Property("CreationResponseContent") + .HasColumnType("varbinary(max)"); + + b.Property("From") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("FromHasDecomposed") + .HasColumnType("bit"); + + b.Property("RelationshipTemplateId") + .IsRequired() + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("To") + .IsRequired() + .HasMaxLength(80) + .IsUnicode(false) + .HasColumnType("varchar(80)") + .IsFixedLength(false); + + b.Property("ToHasDecomposed") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("From"); + + b.HasIndex("RelationshipTemplateId"); + + b.HasIndex("To"); + + b.ToTable("Relationships", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.RelationshipAuditLogEntry", 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("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("Reason") + .HasColumnType("int"); + + b.Property("RelationshipId") + .HasMaxLength(20) + .IsUnicode(false) + .HasColumnType("char(20)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.HasIndex("RelationshipId"); + + b.ToTable("RelationshipAuditLog", "Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplateAllocation", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", null) + .WithMany("Allocations") + .HasForeignKey("RelationshipTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", "RelationshipTemplate") + .WithMany("Relationships") + .HasForeignKey("RelationshipTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("RelationshipTemplate"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.RelationshipAuditLogEntry", b => + { + b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", null) + .WithMany("AuditLog") + .HasForeignKey("RelationshipId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => + { + b.Navigation("Allocations"); + + b.Navigation("Relationships"); + }); + + modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", b => + { + b.Navigation("AuditLog"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.cs new file mode 100644 index 0000000000..4a264237be --- /dev/null +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/20240703095959_ConfigureRelationshipAuditLogDeleteBehavior.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backbone.Modules.Relationships.Infrastructure.Database.SqlServer.Migrations +{ + /// + public partial class ConfigureRelationshipAuditLogDeleteBehavior : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog"); + + migrationBuilder.AddForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog", + column: "RelationshipId", + principalSchema: "Relationships", + principalTable: "Relationships", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog"); + + migrationBuilder.AddForeignKey( + name: "FK_RelationshipAuditLog_Relationships_RelationshipId", + schema: "Relationships", + table: "RelationshipAuditLog", + column: "RelationshipId", + principalSchema: "Relationships", + principalTable: "Relationships", + principalColumn: "Id"); + } + } +} diff --git a/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/RelationshipsDbContextModelSnapshot.cs b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/RelationshipsDbContextModelSnapshot.cs index 31397a7551..e4961b6148 100644 --- a/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/RelationshipsDbContextModelSnapshot.cs +++ b/Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Migrations/RelationshipsDbContextModelSnapshot.cs @@ -233,7 +233,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Backbone.Modules.Relationships.Domain.Aggregates.Relationships.Relationship", null) .WithMany("AuditLog") - .HasForeignKey("RelationshipId"); + .HasForeignKey("RelationshipId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates.RelationshipTemplate", b => diff --git a/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/EntityTypeConfigurations/RelationshipEntityTypeConfiguration.cs b/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/EntityTypeConfigurations/RelationshipEntityTypeConfiguration.cs index a4ed3d3a9f..af8a5d773d 100644 --- a/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/EntityTypeConfigurations/RelationshipEntityTypeConfiguration.cs +++ b/Modules/Relationships/src/Relationships.Infrastructure/Persistence/Database/EntityTypeConfigurations/RelationshipEntityTypeConfiguration.cs @@ -1,5 +1,6 @@ using Backbone.BuildingBlocks.Infrastructure.Persistence.Database.EntityTypeConfigurations; using Backbone.Modules.Relationships.Domain.Aggregates.Relationships; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Backbone.Modules.Relationships.Infrastructure.Persistence.Database.EntityTypeConfigurations; @@ -22,5 +23,7 @@ public override void Configure(EntityTypeBuilder builder) builder.Property(x => x.CreationContent); builder.Property(x => x.CreationResponseContent); + + builder.HasMany(r => r.AuditLog).WithOne().OnDelete(DeleteBehavior.Cascade); } } diff --git a/Modules/Relationships/test/Relationships.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Relationships/test/Relationships.Application.Tests/Tests/Identities/IdentityDeleterTests.cs index 3b8e9cf480..f25896b4fb 100644 --- a/Modules/Relationships/test/Relationships.Application.Tests/Tests/Identities/IdentityDeleterTests.cs +++ b/Modules/Relationships/test/Relationships.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -1,4 +1,7 @@ -using Backbone.Modules.Relationships.Application.Identities; +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Relationships.Application.Identities; +using Backbone.Modules.Relationships.Application.Relationships.Commands.DeleteRelationshipsOfIdentity; +using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplateAllocationsAllocatedByIdentity; using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.DeleteRelationshipTemplatesOfIdentity; using Backbone.UnitTestTools.BaseClasses; using FakeItEasy; @@ -7,22 +10,49 @@ using static Backbone.UnitTestTools.Data.TestDataGenerator; namespace Backbone.Modules.Relationships.Application.Tests.Tests.Identities; + public class IdentityDeleterTests : AbstractTestsBase { [Fact] public async Task Deleter_calls_correct_command() { // Arrange - var identityAddress = CreateRandomIdentityAddress(); var mockMediator = A.Fake(); - var deleter = new IdentityDeleter(mockMediator); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(mockMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); // Act await deleter.Delete(identityAddress); // Assert + A.CallTo(() => mockMediator.Send( + A.That.Matches(i => i.IdentityAddress == identityAddress), + A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => mockMediator.Send( A.That.Matches(i => i.IdentityAddress == identityAddress), A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockMediator.Send( + A.That.Matches(i => i.IdentityAddress == identityAddress), + A._)).MustHaveHappenedOnceExactly(); + } + + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Relationships")).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplates")).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplateAllocations")).MustHaveHappenedOnceExactly(); } } diff --git a/Modules/Synchronization/src/Synchronization.Application/Identities/IdentityDeleter.cs b/Modules/Synchronization/src/Synchronization.Application/Identities/IdentityDeleter.cs index dd1595800f..4014f10596 100644 --- a/Modules/Synchronization/src/Synchronization.Application/Identities/IdentityDeleter.cs +++ b/Modules/Synchronization/src/Synchronization.Application/Identities/IdentityDeleter.cs @@ -6,19 +6,25 @@ using MediatR; namespace Backbone.Modules.Synchronization.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteExternalEventsOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "ExternalEvents"); await _mediator.Send(new DeleteSyncRunsOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "SyncRuns"); await _mediator.Send(new DeleteDatawalletsOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Datawallets"); } } diff --git a/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/Identities/IdentityDeleterTests.cs new file mode 100644 index 0000000000..478497d83e --- /dev/null +++ b/Modules/Synchronization/test/Synchronization.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -0,0 +1,51 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Synchronization.Application.Datawallets.Commands.DeleteDatawalletsOfIdentity; +using Backbone.Modules.Synchronization.Application.Identities; +using Backbone.Modules.Synchronization.Application.SyncRuns.Commands.DeleteExternalEventsOfIdentity; +using Backbone.Modules.Synchronization.Application.SyncRuns.Commands.DeleteSyncRunsOfIdentity; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using MediatR; +using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; + +namespace Backbone.Modules.Synchronization.Application.Tests.Tests.Identities; + +public class IdentityDeleterTests : AbstractTestsBase +{ + [Fact] + public async Task Deleter_calls_correct_command() + { + // Arrange + var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "ExternalEvents")).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "SyncRuns")).MustHaveHappenedOnceExactly(); + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Datawallets")).MustHaveHappenedOnceExactly(); + } +} diff --git a/Modules/Tokens/src/Tokens.Application/Identities/IdentityDeleter.cs b/Modules/Tokens/src/Tokens.Application/Identities/IdentityDeleter.cs index 342873f8c5..895709831b 100644 --- a/Modules/Tokens/src/Tokens.Application/Identities/IdentityDeleter.cs +++ b/Modules/Tokens/src/Tokens.Application/Identities/IdentityDeleter.cs @@ -4,17 +4,21 @@ using MediatR; namespace Backbone.Modules.Tokens.Application.Identities; + public class IdentityDeleter : IIdentityDeleter { private readonly IMediator _mediator; + private readonly IDeletionProcessLogger _deletionProcessLogger; - public IdentityDeleter(IMediator mediator) + public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProcessLogger) { _mediator = mediator; + _deletionProcessLogger = deletionProcessLogger; } public async Task Delete(IdentityAddress identityAddress) { await _mediator.Send(new DeleteTokensOfIdentityCommand(identityAddress)); + await _deletionProcessLogger.LogDeletion(identityAddress, "Tokens"); } } diff --git a/Modules/Tokens/test/Tokens.Application.Tests/Tests/Identities/IdentityDeleterTests.cs b/Modules/Tokens/test/Tokens.Application.Tests/Tests/Identities/IdentityDeleterTests.cs new file mode 100644 index 0000000000..1716b5b1f1 --- /dev/null +++ b/Modules/Tokens/test/Tokens.Application.Tests/Tests/Identities/IdentityDeleterTests.cs @@ -0,0 +1,45 @@ +using Backbone.BuildingBlocks.Application.Identities; +using Backbone.Modules.Tokens.Application.Identities; +using Backbone.Modules.Tokens.Application.Tokens.Commands.DeleteTokensOfIdentity; +using Backbone.UnitTestTools.BaseClasses; +using FakeItEasy; +using MediatR; +using Xunit; +using static Backbone.UnitTestTools.Data.TestDataGenerator; + +namespace Backbone.Modules.Tokens.Application.Tests.Tests.Identities; + +public class IdentityDeleterTests : AbstractTestsBase +{ + [Fact] + public async Task Deleter_calls_correct_command() + { + // Arrange + var mockMediator = A.Fake(); + var dummyIDeletionProcessLogger = A.Dummy(); + var deleter = new IdentityDeleter(mockMediator, dummyIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockMediator.Send(A.That.Matches(command => command.IdentityAddress == identityAddress), A._)).MustHaveHappened(); + } + + [Fact] + public async Task Deleter_correctly_creates_audit_log() + { + // Arrange + var dummyMediator = A.Dummy(); + var mockIDeletionProcessLogger = A.Fake(); + var deleter = new IdentityDeleter(dummyMediator, mockIDeletionProcessLogger); + var identityAddress = CreateRandomIdentityAddress(); + + // Act + await deleter.Delete(identityAddress); + + // Assert + A.CallTo(() => mockIDeletionProcessLogger.LogDeletion(identityAddress, "Tokens")).MustHaveHappenedOnceExactly(); + } +} diff --git a/appsettings.override.json b/appsettings.override.json index 67c96a62c1..a9d9068ce8 100644 --- a/appsettings.override.json +++ b/appsettings.override.json @@ -7,7 +7,8 @@ }, "Cors": { "AllowedOrigins": "http://localhost:8080;https://localhost:8080", - "ExposedHeaders": "" + "ExposedHeaders": "", + "AccessControlAllowCredentials": true }, "SwaggerUi": { "TokenUrl": "http://localhost:5000/connect/token",