diff --git a/.ci/docker-compose.test.yml b/.ci/docker-compose.test.yml index 2056debb5a..ae822013af 100644 --- a/.ci/docker-compose.test.yml +++ b/.ci/docker-compose.test.yml @@ -44,6 +44,22 @@ services: - source: Config target: app/appsettings.override.json + event-handler-service: + container_name: event-handler-service-test + build: + context: .. + dockerfile: EventHandlerService/src/EventHandlerService/Dockerfile + environment: + - ASPNETCORE_ENVIRONMENT=Development + depends_on: + database: + condition: service_started + rabbitmq: + condition: service_started + configs: + - source: Config + target: app/appsettings.override.json + ### infrastructure ### azure-storage-emulator: diff --git a/.ci/eh/buildContainerImage.js b/.ci/eh/buildContainerImage.js new file mode 100755 index 0000000000..24a74c0ae4 --- /dev/null +++ b/.ci/eh/buildContainerImage.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +import { $ } from "zx"; +import { getRequiredEnvVar } from "../lib.js"; + +const tag = getRequiredEnvVar("TAG"); + +const platforms = process.env.PLATFORMS ?? "linux/amd64,linux/arm64"; +const push = process.env.PUSH === "1" ? ["--push", "--provenance=true", "--sbom=true"] : ""; + +await $`docker buildx build --file ./EventHandlerService/src/EventHandlerService/Dockerfile --tag ghcr.io/nmshd/backbone-event-handler:${tag} --platform ${platforms} ${push} .`; diff --git a/.ci/helm/buildChart.js b/.ci/helm/buildChart.js index 2f9eb9e25a..ed917a0e9e 100755 --- a/.ci/helm/buildChart.js +++ b/.ci/helm/buildChart.js @@ -6,4 +6,8 @@ import { getRequiredEnvVar } from "../lib.js"; const version = getRequiredEnvVar("VERSION"); await $`helm dependency update helm`; + +// replace <> with the value of `version` in all Chart.yaml files in helm folder +await $`find helm -name Chart.yaml -exec sed -i -e 's/__app_version__/${version}/g' {} +`; + await $`helm package --version ${version} helm`; diff --git a/.github/workflows/publish-acli.yml b/.github/workflows/publish-acli.yml deleted file mode 100644 index 309ca8ac14..0000000000 --- a/.github/workflows/publish-acli.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Publish Admin CLI Container Image - -on: - push: - tags: - - "acli/*" - -jobs: - publish-acli-container-image: - name: Publish Admin CLI Container Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install script dependencies - run: npm install --prefix ./.ci - - name: Docker Login - uses: docker/login-action@v3.0.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from git tag - id: extract-version-from-git-tag - run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT - env: - GIT_TAG: ${{ github.ref_name }} - - name: Log in to Docker Hub for accessing the cloud builder - uses: docker/login-action@v3 - with: - username: ${{ secrets.CLOUD_BUILDER_USERNAME }} - password: ${{ secrets.CLOUD_BUILDER_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: "lab:latest" - driver: cloud - endpoint: "jssoft/js-soft" - - name: Build and Push Container Image - run: ./.ci/acli/buildContainerImage.js - env: - TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} - PUSH: 1 diff --git a/.github/workflows/publish-aui.yml b/.github/workflows/publish-aui.yml deleted file mode 100644 index ef7c533d76..0000000000 --- a/.github/workflows/publish-aui.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Publish Admin UI Container Image - -on: - push: - tags: - - "aui/*" - -jobs: - publish-aui-container-image: - name: Publish Admin UI Container Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install script dependencies - run: npm install --prefix ./.ci - - name: Docker Login - uses: docker/login-action@v3.0.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from git tag - id: extract-version-from-git-tag - run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT - env: - GIT_TAG: ${{ github.ref_name }} - - name: Log in to Docker Hub for accessing the cloud builder - uses: docker/login-action@v3 - with: - username: ${{ secrets.CLOUD_BUILDER_USERNAME }} - password: ${{ secrets.CLOUD_BUILDER_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: "lab:latest" - driver: cloud - endpoint: "jssoft/js-soft" - - name: Build and Push Container Image - run: ./.ci/aui/buildContainerImage.js - env: - TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} - PUSH: 1 diff --git a/.github/workflows/publish-capi.yml b/.github/workflows/publish-capi.yml deleted file mode 100644 index a317e8b83b..0000000000 --- a/.github/workflows/publish-capi.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Publish Consumer API Container Image - -on: - push: - tags: - - "capi/*" - -jobs: - publish-capi-container-image: - name: Publish Consumer API Container Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install script dependencies - run: npm install --prefix ./.ci - - name: Docker Login - uses: docker/login-action@v3.0.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from git tag - id: extract-version-from-git-tag - run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT - env: - GIT_TAG: ${{ github.ref_name }} - - name: Log in to Docker Hub for accessing the cloud builder - uses: docker/login-action@v3 - with: - username: ${{ secrets.CLOUD_BUILDER_USERNAME }} - password: ${{ secrets.CLOUD_BUILDER_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: "lab:latest" - driver: cloud - endpoint: "jssoft/js-soft" - - name: Build and Push Container Image - run: ./.ci/capi/buildContainerImage.js - env: - TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} - PUSH: 1 diff --git a/.github/workflows/publish-fsc.yml b/.github/workflows/publish-fsc.yml deleted file mode 100644 index dada44bcf5..0000000000 --- a/.github/workflows/publish-fsc.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Publish Files Sanity Check Container Image - -on: - push: - tags: - - "fsc/*" - -jobs: - publish-fsc-container-image: - name: Publish Files Sanity Check Container Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install script dependencies - run: npm install --prefix ./.ci - - name: Docker Login - uses: docker/login-action@v3.0.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from git tag - id: extract-version-from-git-tag - run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT - env: - GIT_TAG: ${{ github.ref_name }} - - name: Log in to Docker Hub for accessing the cloud builder - uses: docker/login-action@v3 - with: - username: ${{ secrets.CLOUD_BUILDER_USERNAME }} - password: ${{ secrets.CLOUD_BUILDER_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: "lab:latest" - driver: cloud - endpoint: "jssoft/js-soft" - - name: Build and Push Container Image - run: ./.ci/sc/buildContainerImage.js - env: - TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} - PUSH: 1 diff --git a/.github/workflows/publish-helm.yml b/.github/workflows/publish-helm.yml deleted file mode 100644 index 3b5cf5ca9b..0000000000 --- a/.github/workflows/publish-helm.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Publish Helm Chart - -on: - push: - tags: - - "helm/*" - -jobs: - publish-helm-chart: - name: Publish Helm Chart - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install script dependencies - run: npm install --prefix ./.ci - - run: echo "$PASSWORD" | helm registry login -u $USER --password-stdin https://ghcr.io - env: - USER: ${{ github.actor }} - PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from git tag - id: extract-version-from-git-tag - run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT - env: - GIT_TAG: ${{ github.ref_name }} - - name: Build Helm Chart - run: ./.ci/helm/buildChart.js - env: - VERSION: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} - - name: Push Helm Chart - run: ./.ci/helm/pushChart.js - env: - VERSION: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..eef6319349 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,196 @@ +name: Publish Helm Chart + +on: + push: + tags: "*" + +jobs: + publish-admin-cli: + name: Publish Admin CLI Container Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - name: Docker Login + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract version from git tag + id: extract-version-from-git-tag + run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT + env: + GIT_TAG: ${{ github.ref_name }} + - name: Log in to Docker Hub for accessing the cloud builder + uses: docker/login-action@v3 + with: + username: ${{ secrets.CLOUD_BUILDER_USERNAME }} + password: ${{ secrets.CLOUD_BUILDER_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "jssoft/js-soft" + - name: Build and Push Container Image + run: ./.ci/acli/buildContainerImage.js + env: + TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} + PUSH: 1 + + publish-admin-ui: + name: Publish Admin UI Container Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - name: Docker Login + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract version from git tag + id: extract-version-from-git-tag + run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT + env: + GIT_TAG: ${{ github.ref_name }} + - name: Log in to Docker Hub for accessing the cloud builder + uses: docker/login-action@v3 + with: + username: ${{ secrets.CLOUD_BUILDER_USERNAME }} + password: ${{ secrets.CLOUD_BUILDER_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "jssoft/js-soft" + - name: Build and Push Container Image + run: ./.ci/aui/buildContainerImage.js + env: + TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} + PUSH: 1 + + publish-consumer-api: + name: Publish Consumer API Container Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - name: Docker Login + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract version from git tag + id: extract-version-from-git-tag + run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT + env: + GIT_TAG: ${{ github.ref_name }} + - name: Log in to Docker Hub for accessing the cloud builder + uses: docker/login-action@v3 + with: + username: ${{ secrets.CLOUD_BUILDER_USERNAME }} + password: ${{ secrets.CLOUD_BUILDER_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "jssoft/js-soft" + - name: Build and Push Container Image + run: ./.ci/capi/buildContainerImage.js + env: + TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} + PUSH: 1 + + publish-event-handler: + name: Publish Event Handler Service Container Image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - name: Docker Login + uses: docker/login-action@v3.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract version from git tag + id: extract-version-from-git-tag + run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT + env: + GIT_TAG: ${{ github.ref_name }} + - name: Log in to Docker Hub for accessing the cloud builder + uses: docker/login-action@v3 + with: + username: ${{ secrets.CLOUD_BUILDER_USERNAME }} + password: ${{ secrets.CLOUD_BUILDER_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "jssoft/js-soft" + - name: Build and Push Container Image + run: ./.ci/eh/buildContainerImage.js + env: + TAG: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} + PUSH: 1 + + publish-helm-chart: + name: Publish Helm Chart + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: + - publish-admin-cli + - publish-admin-ui + - publish-consumer-api + - publish-event-handler + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - run: echo "$PASSWORD" | helm registry login -u $USER --password-stdin https://ghcr.io + env: + USER: ${{ github.actor }} + PASSWORD: ${{ secrets.GITHUB_TOKEN }} + - name: Extract version from git tag + id: extract-version-from-git-tag + run: echo "VERSION=$(./.ci/extractVersionFromGitTag.js)" >> $GITHUB_OUTPUT + env: + GIT_TAG: ${{ github.ref_name }} + - name: Build Helm Chart + run: ./.ci/helm/buildChart.js + env: + VERSION: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} + - name: Push Helm Chart + run: ./.ci/helm/pushChart.js + env: + VERSION: ${{ steps.extract-version-from-git-tag.outputs.VERSION }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d9649d455..fe5a80906c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -205,6 +205,22 @@ jobs: TAG: test PLATFORMS: linux/amd64 + build-eh-container-image: + name: Build Event Handler Service Container Image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install script dependencies + run: npm install --prefix ./.ci + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build image + run: ./.ci/eh/buildContainerImage.js + env: + TAG: test + PLATFORMS: linux/amd64 + build-aui-container-image: name: Build Admin UI Container Image runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ee910f9a14..6896a656f9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# mono +.mono + # Build results [Dd]ebug/ [Dd]ebugPublic/ diff --git a/.run/Multi-Launch_ Backbone.run.xml b/.run/Multi-Launch_ Backbone.run.xml index fdab94a302..d192795ae4 100644 --- a/.run/Multi-Launch_ Backbone.run.xml +++ b/.run/Multi-Launch_ Backbone.run.xml @@ -38,6 +38,18 @@ + + + + diff --git a/AdminApi/src/AdminApi/Properties/launchSettings.json b/AdminApi/src/AdminApi/Properties/launchSettings.json index b8aa19bc06..eafcc4c465 100644 --- a/AdminApi/src/AdminApi/Properties/launchSettings.json +++ b/AdminApi/src/AdminApi/Properties/launchSettings.json @@ -6,7 +6,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Local", "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy", - "ASPNETCORE_URLS": "http://localhost:8082" + "ASPNETCORE_URLS": "http://*:8082" } } } diff --git a/AdminApi/test/AdminApi.Tests.Integration/AdminApi.Tests.Integration.csproj b/AdminApi/test/AdminApi.Tests.Integration/AdminApi.Tests.Integration.csproj index 377c0dccb8..7893f6f3ef 100644 --- a/AdminApi/test/AdminApi.Tests.Integration/AdminApi.Tests.Integration.csproj +++ b/AdminApi/test/AdminApi.Tests.Integration/AdminApi.Tests.Integration.csproj @@ -5,13 +5,14 @@ - + + diff --git a/AdminApi/test/AdminApi.Tests.Integration/Models/ChangeClientSecretResponse.cs b/AdminApi/test/AdminApi.Tests.Integration/Models/ChangeClientSecretResponse.cs index e9ad74911e..6ad1825d25 100644 --- a/AdminApi/test/AdminApi.Tests.Integration/Models/ChangeClientSecretResponse.cs +++ b/AdminApi/test/AdminApi.Tests.Integration/Models/ChangeClientSecretResponse.cs @@ -1,9 +1,11 @@ namespace Backbone.AdminApi.Tests.Integration.Models; + public class ChangeClientSecretResponse { public required string ClientId { get; set; } public required string DisplayName { get; set; } public required string ClientSecret { get; set; } + public required string DefaultTier { get; set; } public required DateTime CreatedAt { get; set; } public int? MaxIdentities { get; set; } } diff --git a/AdminApi/test/AdminApi.Tests.Integration/Support/JsonValidators.cs b/AdminApi/test/AdminApi.Tests.Integration/Support/JsonValidators.cs index ba4137df39..996ea8a26c 100644 --- a/AdminApi/test/AdminApi.Tests.Integration/Support/JsonValidators.cs +++ b/AdminApi/test/AdminApi.Tests.Integration/Support/JsonValidators.cs @@ -1,7 +1,9 @@ +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; -using Newtonsoft.Json.Schema.Generation; using Newtonsoft.Json.Serialization; +using NJsonSchema.NewtonsoftJson.Generation; +using JsonSchemaGenerator = NJsonSchema.Generation.JsonSchemaGenerator; namespace Backbone.AdminApi.Tests.Integration.Support; @@ -17,14 +19,28 @@ public static bool ValidateJsonSchema(string json, out IList errors) return parsedJson.IsValid(schema, out errors); } - var generator = new JSchemaGenerator + var settings = new NewtonsoftJsonSchemaGeneratorSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + SerializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + } }; + + var generator = new JsonSchemaGenerator(settings); + var schemaJson = generator.Generate(typeof(T)); - schema = JSchema.Parse(schemaJson.ToString()); + + var generatedSchema = schemaJson.ToJson(); + + schema = JSchema.Parse(generatedSchema); + + schema.AllowAdditionalProperties = true; + CACHED_SCHEMAS.Add(typeof(T), schema); + var responseJson = JObject.Parse(json); + return responseJson.IsValid(schema, out errors); } } diff --git a/Backbone.sln b/Backbone.sln index af7c799340..79075c4d6c 100644 --- a/Backbone.sln +++ b/Backbone.sln @@ -287,6 +287,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Files.Infrastructure.Tests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messages.Domain.Tests", "Modules\Messages\test\Messages.Domain.Tests\Messages.Domain.Tests.csproj", "{83A86879-670B-4F22-8835-EE1D0AB49AA9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventHandlerService", "EventHandlerService", "{11FE034C-FA73-4766-99DB-D2C606018934}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D54A9259-7708-45C1-B8D9-448B97F43B80}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2429FCAB-2058-4403-94DA-DA26B1655A7D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventHandlerService", "EventHandlerService\src\EventHandlerService\EventHandlerService.csproj", "{B4664E79-77A5-41AE-8480-C449ED5B2576}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventHandlerService.Tests", "EventHandlerService\test\EventHandlerService.Tests\EventHandlerService.Tests.csproj", "{D47E0FE1-23A0-4A96-AF57-E3CE71A132AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -697,6 +707,14 @@ Global {83A86879-670B-4F22-8835-EE1D0AB49AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU {83A86879-670B-4F22-8835-EE1D0AB49AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU {83A86879-670B-4F22-8835-EE1D0AB49AA9}.Release|Any CPU.Build.0 = Release|Any CPU + {B4664E79-77A5-41AE-8480-C449ED5B2576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4664E79-77A5-41AE-8480-C449ED5B2576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4664E79-77A5-41AE-8480-C449ED5B2576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4664E79-77A5-41AE-8480-C449ED5B2576}.Release|Any CPU.Build.0 = Release|Any CPU + {D47E0FE1-23A0-4A96-AF57-E3CE71A132AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D47E0FE1-23A0-4A96-AF57-E3CE71A132AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D47E0FE1-23A0-4A96-AF57-E3CE71A132AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D47E0FE1-23A0-4A96-AF57-E3CE71A132AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -828,6 +846,10 @@ Global {9AB91A36-1E62-490A-9CF3-2137CA15658F} = {4F812E23-62EB-4B79-8ECA-5CA72CF5D3BC} {EE7F27E9-9DD9-41FC-923D-05D595829A03} = {2D0BC8E9-ED6B-49D9-937C-1616ED40FB3E} {83A86879-670B-4F22-8835-EE1D0AB49AA9} = {BBE908B0-D642-4002-8A88-9F1726BA8CB6} + {D54A9259-7708-45C1-B8D9-448B97F43B80} = {11FE034C-FA73-4766-99DB-D2C606018934} + {2429FCAB-2058-4403-94DA-DA26B1655A7D} = {11FE034C-FA73-4766-99DB-D2C606018934} + {B4664E79-77A5-41AE-8480-C449ED5B2576} = {D54A9259-7708-45C1-B8D9-448B97F43B80} + {D47E0FE1-23A0-4A96-AF57-E3CE71A132AD} = {2429FCAB-2058-4403-94DA-DA26B1655A7D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1F3BD2C6-7CB3-450F-A21A-23EA520D5B7A} diff --git a/Backbone.slnLaunch b/Backbone.slnLaunch index 7201a9c9e8..477961e63b 100644 --- a/Backbone.slnLaunch +++ b/Backbone.slnLaunch @@ -1,6 +1,6 @@ [ { - "Name": "Admin UI \u002B Consumer API", + "Name": "Backbone (Full)", "Projects": [ { "Name": "ConsumerApi\\ConsumerApi.csproj", @@ -16,7 +16,12 @@ "Name": "AdminApi\\src\\AdminApi\\AdminApi.csproj", "Action": "Start", "DebugTarget": "" + }, + { + "Name": "EventHandlerService\\src\\EventHandlerService\\EventHandlerService.csproj", + "Action": "Start", + "DebugTarget": "" } ] } -] +] \ No newline at end of file diff --git a/ConsumerApi.Tests.Integration/ConsumerApi.Tests.Integration.csproj b/ConsumerApi.Tests.Integration/ConsumerApi.Tests.Integration.csproj index 66336fca88..859036c8f5 100644 --- a/ConsumerApi.Tests.Integration/ConsumerApi.Tests.Integration.csproj +++ b/ConsumerApi.Tests.Integration/ConsumerApi.Tests.Integration.csproj @@ -5,7 +5,7 @@ - + @@ -13,6 +13,7 @@ + diff --git a/ConsumerApi.Tests.Integration/Support/JsonValidators.cs b/ConsumerApi.Tests.Integration/Support/JsonValidators.cs index fc5858a5e9..31c03b152e 100644 --- a/ConsumerApi.Tests.Integration/Support/JsonValidators.cs +++ b/ConsumerApi.Tests.Integration/Support/JsonValidators.cs @@ -1,7 +1,9 @@ +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; -using Newtonsoft.Json.Schema.Generation; using Newtonsoft.Json.Serialization; +using NJsonSchema.NewtonsoftJson.Generation; +using JsonSchemaGenerator = NJsonSchema.Generation.JsonSchemaGenerator; namespace Backbone.ConsumerApi.Tests.Integration.Support; @@ -17,14 +19,28 @@ public static bool ValidateJsonSchema(string json, out IList errors) return parsedJson.IsValid(schema, out errors); } - var generator = new JSchemaGenerator + var settings = new NewtonsoftJsonSchemaGeneratorSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + SerializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + } }; + + var generator = new JsonSchemaGenerator(settings); + var schemaJson = generator.Generate(typeof(T)); - schema = JSchema.Parse(schemaJson.ToString()); + + var generatedSchema = schemaJson.ToJson(); + + schema = JSchema.Parse(generatedSchema); + + schema.AllowAdditionalProperties = true; + CACHED_SCHEMAS.Add(typeof(T), schema); + var responseJson = JObject.Parse(json); + return responseJson.IsValid(schema, out errors); } } diff --git a/ConsumerApi/Program.cs b/ConsumerApi/Program.cs index c752ac59e5..cad2de10e4 100644 --- a/ConsumerApi/Program.cs +++ b/ConsumerApi/Program.cs @@ -2,7 +2,6 @@ using Autofac.Extensions.DependencyInjection; using Backbone.BuildingBlocks.API; using Backbone.BuildingBlocks.API.Extensions; -using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; using Backbone.BuildingBlocks.Application.QuotaCheck; using Backbone.BuildingBlocks.Infrastructure.Persistence.Database; using Backbone.Common.Infrastructure; @@ -218,16 +217,6 @@ static void Configure(WebApplication app) }); app.UseResponseCaching(); - - var eventBus = app.Services.GetRequiredService(); - var modules = app.Services.GetRequiredService>(); - - foreach (var module in modules) - { - module.ConfigureEventBus(eventBus); - } - - eventBus.StartConsuming(); } static void LoadConfiguration(WebApplicationBuilder webApplicationBuilder, string[] strings) diff --git a/ConsumerApi/Properties/launchSettings.json b/ConsumerApi/Properties/launchSettings.json index b26d42544d..1b2e134ac1 100644 --- a/ConsumerApi/Properties/launchSettings.json +++ b/ConsumerApi/Properties/launchSettings.json @@ -4,9 +4,9 @@ "Default": { "commandName": "Project", "environmentVariables": { - "Modules__Files__Infrastructure__BlobStorage__ConnectionInfo": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;", + "Modules__Files__Infrastructure__BlobStorage__ConnectionInfo": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;", "ASPNETCORE_ENVIRONMENT": "Local", - "ASPNETCORE_URLS": "http://localhost:8081" + "ASPNETCORE_URLS": "http://*:8081" } } } diff --git a/EventHandlerService/src/EventHandlerService/Dockerfile b/EventHandlerService/src/EventHandlerService/Dockerfile new file mode 100644 index 0000000000..393a62461b --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/Dockerfile @@ -0,0 +1,87 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.18 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ["Directory.Build.props", "."] +COPY ["Modules/Directory.Build.props", "Modules/"] +COPY ["EventHandlerService/src/EventHandlerService/EventHandlerService.csproj", "EventHandlerService/src/EventHandlerService/"] +COPY ["BuildingBlocks/src/BuildingBlocks.Infrastructure/BuildingBlocks.Infrastructure.csproj", "BuildingBlocks/src/BuildingBlocks.Infrastructure/"] +COPY ["BuildingBlocks/src/BuildingBlocks.Application.Abstractions/BuildingBlocks.Application.Abstractions.csproj", "BuildingBlocks/src/BuildingBlocks.Application.Abstractions/"] +COPY ["BuildingBlocks/src/DevelopmentKit.Identity/DevelopmentKit.Identity.csproj", "BuildingBlocks/src/DevelopmentKit.Identity/"] +COPY ["BuildingBlocks/src/BuildingBlocks.Domain/BuildingBlocks.Domain.csproj", "BuildingBlocks/src/BuildingBlocks.Domain/"] +COPY ["BuildingBlocks/src/Tooling/Tooling.csproj", "BuildingBlocks/src/Tooling/"] +COPY ["Infrastructure/Infrastructure.csproj", "Infrastructure/"] +COPY ["Modules/Challenges/src/Challenges.ConsumerApi/Challenges.ConsumerApi.csproj", "Modules/Challenges/src/Challenges.ConsumerApi/"] +COPY ["BuildingBlocks/src/BuildingBlocks.API/BuildingBlocks.API.csproj", "BuildingBlocks/src/BuildingBlocks.API/"] +COPY ["Modules/Devices/src/Devices.Domain/Devices.Domain.csproj", "Modules/Devices/src/Devices.Domain/"] +COPY ["Modules/Devices/src/Devices.Infrastructure/Devices.Infrastructure.csproj", "Modules/Devices/src/Devices.Infrastructure/"] +COPY ["Modules/Devices/src/Devices.Application/Devices.Application.csproj", "Modules/Devices/src/Devices.Application/"] +COPY ["BuildingBlocks/src/BuildingBlocks.Application/BuildingBlocks.Application.csproj", "BuildingBlocks/src/BuildingBlocks.Application/"] +COPY ["Common/src/Common.Infrastructure/Common.Infrastructure.csproj", "Common/src/Common.Infrastructure/"] +COPY ["BuildingBlocks/src/Crypto/Crypto.csproj", "BuildingBlocks/src/Crypto/"] +COPY ["Modules/Challenges/src/Challenges.Application/Challenges.Application.csproj", "Modules/Challenges/src/Challenges.Application/"] +COPY ["Modules/Challenges/src/Challenges.Domain/Challenges.Domain.csproj", "Modules/Challenges/src/Challenges.Domain/"] +COPY ["Modules/Challenges/src/Challenges.Infrastructure.Database.Postgres/Challenges.Infrastructure.Database.Postgres.csproj", "Modules/Challenges/src/Challenges.Infrastructure.Database.Postgres/"] +COPY ["Modules/Challenges/src/Challenges.Infrastructure/Challenges.Infrastructure.csproj", "Modules/Challenges/src/Challenges.Infrastructure/"] +COPY ["Modules/Challenges/src/Challenges.Infrastructure.Database.SqlServer/Challenges.Infrastructure.Database.SqlServer.csproj", "Modules/Challenges/src/Challenges.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Devices/src/Devices.ConsumerApi/Devices.ConsumerApi.csproj", "Modules/Devices/src/Devices.ConsumerApi/"] +COPY ["Modules/Devices/src/Devices.Infrastructure.Database.Postgres/Devices.Infrastructure.Database.Postgres.csproj", "Modules/Devices/src/Devices.Infrastructure.Database.Postgres/"] +COPY ["Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/Devices.Infrastructure.Database.SqlServer.csproj", "Modules/Devices/src/Devices.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Files/src/Files.ConsumerApi/Files.ConsumerApi.csproj", "Modules/Files/src/Files.ConsumerApi/"] +COPY ["Modules/Files/src/Files.Application/Files.Application.csproj", "Modules/Files/src/Files.Application/"] +COPY ["Modules/Files/src/Files.Domain/Files.Domain.csproj", "Modules/Files/src/Files.Domain/"] +COPY ["Modules/Files/src/Files.Infrastructure.Database.Postgres/Files.Infrastructure.Database.Postgres.csproj", "Modules/Files/src/Files.Infrastructure.Database.Postgres/"] +COPY ["Modules/Files/src/Files.Infrastructure/Files.Infrastructure.csproj", "Modules/Files/src/Files.Infrastructure/"] +COPY ["Modules/Files/src/Files.Infrastructure.Database.SqlServer/Files.Infrastructure.Database.SqlServer.csproj", "Modules/Files/src/Files.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Messages/src/Messages.ConsumerApi/Messages.ConsumerApi.csproj", "Modules/Messages/src/Messages.ConsumerApi/"] +COPY ["Modules/Messages/src/Messages.Application/Messages.Application.csproj", "Modules/Messages/src/Messages.Application/"] +COPY ["Modules/Messages/src/Messages.Domain/Messages.Domain.csproj", "Modules/Messages/src/Messages.Domain/"] +COPY ["Modules/Messages/src/Messages.Infrastructure.Database.Postgres/Messages.Infrastructure.Database.Postgres.csproj", "Modules/Messages/src/Messages.Infrastructure.Database.Postgres/"] +COPY ["Modules/Messages/src/Messages.Infrastructure/Messages.Infrastructure.csproj", "Modules/Messages/src/Messages.Infrastructure/"] +COPY ["Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/Messages.Infrastructure.Database.SqlServer.csproj", "Modules/Messages/src/Messages.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Quotas/src/Quotas.ConsumerApi/Quotas.ConsumerApi.csproj", "Modules/Quotas/src/Quotas.ConsumerApi/"] +COPY ["Modules/Quotas/src/Quotas.Application/Quotas.Application.csproj", "Modules/Quotas/src/Quotas.Application/"] +COPY ["Modules/Quotas/src/Quotas.Domain/Quotas.Domain.csproj", "Modules/Quotas/src/Quotas.Domain/"] +COPY ["Modules/Quotas/src/Quotas.Infrastructure.Database.Postgres/Quotas.Infrastructure.Database.Postgres.csproj", "Modules/Quotas/src/Quotas.Infrastructure.Database.Postgres/"] +COPY ["Modules/Quotas/src/Quotas.Infrastructure/Quotas.Infrastructure.csproj", "Modules/Quotas/src/Quotas.Infrastructure/"] +COPY ["Modules/Quotas/src/Quotas.Infrastructure.Database.SqlServer/Quotas.Infrastructure.Database.SqlServer.csproj", "Modules/Quotas/src/Quotas.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Relationships/src/Relationships.ConsumerApi/Relationships.ConsumerApi.csproj", "Modules/Relationships/src/Relationships.ConsumerApi/"] +COPY ["Modules/Relationships/src/Relationships.Application/Relationships.Application.csproj", "Modules/Relationships/src/Relationships.Application/"] +COPY ["Modules/Relationships/src/Relationships.Common/Relationships.Common.csproj", "Modules/Relationships/src/Relationships.Common/"] +COPY ["Modules/Relationships/src/Relationships.Domain/Relationships.Domain.csproj", "Modules/Relationships/src/Relationships.Domain/"] +COPY ["Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/Relationships.Infrastructure.Database.Postgres.csproj", "Modules/Relationships/src/Relationships.Infrastructure.Database.Postgres/"] +COPY ["Modules/Relationships/src/Relationships.Infrastructure/Relationships.Infrastructure.csproj", "Modules/Relationships/src/Relationships.Infrastructure/"] +COPY ["Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/Relationships.Infrastructure.Database.SqlServer.csproj", "Modules/Relationships/src/Relationships.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Synchronization/src/Synchronization.ConsumerApi/Synchronization.ConsumerApi.csproj", "Modules/Synchronization/src/Synchronization.ConsumerApi/"] +COPY ["Modules/Synchronization/src/Synchronization.Application/Synchronization.Application.csproj", "Modules/Synchronization/src/Synchronization.Application/"] +COPY ["Modules/Synchronization/src/Synchronization.Domain/Synchronization.Domain.csproj", "Modules/Synchronization/src/Synchronization.Domain/"] +COPY ["Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/Synchronization.Infrastructure.Database.Postgres.csproj", "Modules/Synchronization/src/Synchronization.Infrastructure.Database.Postgres/"] +COPY ["Modules/Synchronization/src/Synchronization.Infrastructure/Synchronization.Infrastructure.csproj", "Modules/Synchronization/src/Synchronization.Infrastructure/"] +COPY ["Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/Synchronization.Infrastructure.Database.SqlServer.csproj", "Modules/Synchronization/src/Synchronization.Infrastructure.Database.SqlServer/"] +COPY ["Modules/Tokens/src/Tokens.ConsumerApi/Tokens.ConsumerApi.csproj", "Modules/Tokens/src/Tokens.ConsumerApi/"] +COPY ["Modules/Tokens/src/Tokens.Application/Tokens.Application.csproj", "Modules/Tokens/src/Tokens.Application/"] +COPY ["Modules/Tokens/src/Tokens.Domain/Tokens.Domain.csproj", "Modules/Tokens/src/Tokens.Domain/"] +COPY ["Modules/Tokens/src/Tokens.Infrastructure.Database.Postgres/Tokens.Infrastructure.Database.Postgres.csproj", "Modules/Tokens/src/Tokens.Infrastructure.Database.Postgres/"] +COPY ["Modules/Tokens/src/Tokens.Infrastructure/Tokens.Infrastructure.csproj", "Modules/Tokens/src/Tokens.Infrastructure/"] +COPY ["Modules/Tokens/src/Tokens.Infrastructure.Database.SqlServer/Tokens.Infrastructure.Database.SqlServer.csproj", "Modules/Tokens/src/Tokens.Infrastructure.Database.SqlServer/"] + +RUN dotnet restore "EventHandlerService/src/EventHandlerService/EventHandlerService.csproj" + +COPY . . + +WORKDIR "/src/EventHandlerService/src/EventHandlerService" +RUN dotnet build "EventHandlerService.csproj" -c Release -o /app/build --no-restore + +FROM build AS publish +RUN dotnet publish -c Release -o /app/publish --no-restore "EventHandlerService.csproj" + +FROM base AS final + +RUN apk add icu-libs +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=0 + +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Backbone.EventHandlerService.dll"] diff --git a/EventHandlerService/src/EventHandlerService/EventHandlerService.cs b/EventHandlerService/src/EventHandlerService/EventHandlerService.cs new file mode 100644 index 0000000000..f0574be97a --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/EventHandlerService.cs @@ -0,0 +1,47 @@ +using Backbone.BuildingBlocks.API; +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; + +namespace Backbone.EventHandlerService; + +public class EventHandlerService : IHostedService +{ + private readonly IEventBus _eventBus; + private readonly IEnumerable _modules; + private readonly ILogger _logger; + + public EventHandlerService(IEventBus eventBus, IEnumerable modules, ILogger logger) + { + _eventBus = eventBus; + _modules = modules; + _logger = logger; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + SubscribeToEvents(); + StartConsuming(); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private void SubscribeToEvents() + { + _logger.LogInformation("Subscribing to events..."); + foreach (var module in _modules) + { + module.ConfigureEventBus(_eventBus); + } + + _logger.LogInformation("Successfully subscribed to events."); + } + + private void StartConsuming() + { + _eventBus.StartConsuming(); + } +} diff --git a/EventHandlerService/src/EventHandlerService/EventHandlerService.csproj b/EventHandlerService/src/EventHandlerService/EventHandlerService.csproj new file mode 100644 index 0000000000..c511107aa1 --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/EventHandlerService.csproj @@ -0,0 +1,29 @@ + + + + Exe + Linux + ..\..\.. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EventHandlerService/src/EventHandlerService/EventServiceConfiguration.cs b/EventHandlerService/src/EventHandlerService/EventServiceConfiguration.cs new file mode 100644 index 0000000000..7e1c47d4e9 --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/EventServiceConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using Backbone.Infrastructure.EventBus; + +namespace Backbone.EventHandlerService; + +public class EventServiceConfiguration +{ + [Required] + public InfrastructureConfiguration Infrastructure { get; set; } = new(); +} + +public class InfrastructureConfiguration +{ + [Required] + public EventBusConfiguration EventBus { get; set; } = new(); +} diff --git a/EventHandlerService/src/EventHandlerService/Program.cs b/EventHandlerService/src/EventHandlerService/Program.cs new file mode 100644 index 0000000000..6cbcd9beeb --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/Program.cs @@ -0,0 +1,115 @@ +using System.Reflection; +using Autofac.Extensions.DependencyInjection; +using Backbone.BuildingBlocks.API.Extensions; +using Backbone.EventHandlerService; +using Backbone.Infrastructure.EventBus; +using Backbone.Modules.Challenges.ConsumerApi; +using Backbone.Modules.Devices.ConsumerApi; +using Backbone.Modules.Devices.Infrastructure.PushNotifications; +using Backbone.Modules.Files.ConsumerApi; +using Backbone.Modules.Messages.ConsumerApi; +using Backbone.Modules.Quotas.ConsumerApi; +using Backbone.Modules.Relationships.ConsumerApi; +using Backbone.Modules.Synchronization.ConsumerApi; +using Backbone.Modules.Tokens.ConsumerApi; +using Microsoft.Extensions.Options; +using Serilog; +using Serilog.Exceptions; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.EntityFrameworkCore.Destructurers; +using Serilog.Settings.Configuration; +using DevicesConfiguration = Backbone.Modules.Devices.ConsumerApi.Configuration; + + +Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); + +try +{ + Log.Information("Creating app..."); + + var app = CreateHostBuilder(args); + + Log.Information("App created."); + Log.Information("Starting app..."); + + await app.Build().RunAsync(); + + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + await Log.CloseAndFlushAsync(); +} + + +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; +#pragma warning restore ASP0000 + + services.AddTransient(); + + services + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration) + .AddModule(configuration); + + services.AddCustomIdentity(hostContext.HostingEnvironment); + + 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", "eventHandlerService") + .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() }) + ) + ); +} diff --git a/EventHandlerService/src/EventHandlerService/appsettings.json b/EventHandlerService/src/EventHandlerService/appsettings.json new file mode 100644 index 0000000000..2bbd40d624 --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/appsettings.json @@ -0,0 +1,95 @@ +{ + "Infrastructure": { + "EventBus": { + "SubscriptionClientName": "consumerapi", + "HandlerRetryBehavior": { + "NumberOfRetries": 5, + "MinimumBackoff": 2, + "MaximumBackoff": 120 + } + } + }, + "Modules": { + "Challenges": {}, + "Quotas": { + "Application": { + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Devices": { + "Application": { + "AddressPrefix": "id1", + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Files": { + "Application": { + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Messages": { + "Application": { + "AddressPrefix": "id1", + "MaxNumberOfUnreceivedMessagesFromOneSender": 20, + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Relationships": { + "Application": { + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Synchronization": { + "Application": { + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + }, + "Tokens": { + "Application": { + "Pagination": { + "DefaultPageSize": 50, + "MaxPageSize": 200 + } + } + } + }, + "Logging": { + "MinimumLevel": { + "Default": "Warning", + "Override": { + "Backbone": "Information", + "Enmeshed": "Information", + "Jobs.IdentityDeletion": "Information", + + "Microsoft": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": { + "Console": { + "Name": "Console", + "Args": { + "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" + } + } + } + } +} diff --git a/EventHandlerService/src/EventHandlerService/appsettings.override.json b/EventHandlerService/src/EventHandlerService/appsettings.override.json new file mode 100644 index 0000000000..40cf03c8d0 --- /dev/null +++ b/EventHandlerService/src/EventHandlerService/appsettings.override.json @@ -0,0 +1,97 @@ +{ + "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=challenges;Password=Passw0rd;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=quotas;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=quotas;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + }, + "Devices": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=devices;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=devices;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + }, + "PushNotifications": { + "Provider": "Dummy" + } + } + }, + "Files": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=files;Password=Passw0rd;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": { + "Infrastructure": { + "SqlDatabase": { + "Provider": "Postgres", + "ConnectionString": "User ID=messages;Password=Passw0rd;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=relationships;Password=Passw0rd;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=synchronization;Password=Passw0rd;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=tokens;Password=Passw0rd;Server=localhost;Port=5432;Database=enmeshed;" // postgres + // "ConnectionString": "Server=localhost;Database=enmeshed;User Id=tokens;Password=Passw0rd;TrustServerCertificate=True" // sqlserver + } + } + } + } +} diff --git a/EventHandlerService/test/EventHandlerService.Tests/EventHandlerService.Tests.csproj b/EventHandlerService/test/EventHandlerService.Tests/EventHandlerService.Tests.csproj new file mode 100644 index 0000000000..6a90f6210d --- /dev/null +++ b/EventHandlerService/test/EventHandlerService.Tests/EventHandlerService.Tests.csproj @@ -0,0 +1,29 @@ + + + + false + true + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + diff --git a/EventHandlerService/test/EventHandlerService.Tests/EventHandlerServiceTests.cs b/EventHandlerService/test/EventHandlerService.Tests/EventHandlerServiceTests.cs new file mode 100644 index 0000000000..cd75a139a4 --- /dev/null +++ b/EventHandlerService/test/EventHandlerService.Tests/EventHandlerServiceTests.cs @@ -0,0 +1,32 @@ +using Backbone.BuildingBlocks.API; +using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.EventBus; +using FakeItEasy; +using Microsoft.Extensions.Logging; + +namespace Backbone.EventHandlerService.Tests; + +public class EventHandlerServiceTests +{ + [Fact] + public async void HappyPath() + { + // Arrange + var mockEventBus = A.Fake(); + + var eventHandlerService = CreateService(mockEventBus); + + // Act + await eventHandlerService.StartAsync(CancellationToken.None); + + // Assert + A.CallTo(() => mockEventBus.StartConsuming()).MustHaveHappenedOnceExactly(); + } + + private static EventHandlerService CreateService(IEventBus eventBus) + { + return new EventHandlerService( + eventBus, + A.Dummy>(), + A.Dummy>()); + } +} diff --git a/Jobs/src/Job.IdentityDeletion/Dockerfile b/Jobs/src/Job.IdentityDeletion/Dockerfile index 07e4d85a3c..dbf0322698 100644 --- a/Jobs/src/Job.IdentityDeletion/Dockerfile +++ b/Jobs/src/Job.IdentityDeletion/Dockerfile @@ -77,6 +77,10 @@ FROM build AS publish RUN dotnet publish -c Release --output /app/publish --no-restore "Job.IdentityDeletion.csproj" FROM base AS final + +RUN apk add icu-libs +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=0 + WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Backbone.Job.IdentityDeletion.dll"] diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 1095aa8030..d2abe8d5f2 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -16,6 +16,9 @@ dependencies: - name: consumerapi version: "*" repository: file://charts/consumerapi + - name: eventhandler + version: "*" + repository: file://charts/eventhandler icon: https://raw.githubusercontent.com/nmshd/nmshd.github.io/main/assets/images/Logo.svg home: https://enmeshed.eu diff --git a/helm/charts/admincli/Chart.yaml b/helm/charts/admincli/Chart.yaml index 29b396a176..54002a6002 100644 --- a/helm/charts/admincli/Chart.yaml +++ b/helm/charts/admincli/Chart.yaml @@ -6,4 +6,4 @@ type: application version: "1.0.0" -appVersion: "v2.2.3" +appVersion: "__appVersion__" diff --git a/helm/charts/adminui/Chart.yaml b/helm/charts/adminui/Chart.yaml index 9b57271b25..76d2c27b25 100644 --- a/helm/charts/adminui/Chart.yaml +++ b/helm/charts/adminui/Chart.yaml @@ -6,4 +6,4 @@ type: application version: "1.0.0" -appVersion: "v2.6.0" +appVersion: "__appVersion__" diff --git a/helm/charts/consumerapi/Chart.yaml b/helm/charts/consumerapi/Chart.yaml index 92e1dc0276..bf0efeb8cf 100644 --- a/helm/charts/consumerapi/Chart.yaml +++ b/helm/charts/consumerapi/Chart.yaml @@ -6,4 +6,4 @@ type: application version: "1.0.0" -appVersion: "v4.5.0" +appVersion: "__appVersion__" diff --git a/helm/charts/eventhandler/Chart.yaml b/helm/charts/eventhandler/Chart.yaml new file mode 100644 index 0000000000..7230025f17 --- /dev/null +++ b/helm/charts/eventhandler/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: eventhandler +description: A Helm chart for Kubernetes + +type: application + +version: "1.0.0" + +appVersion: "__appVersion__" diff --git a/helm/charts/eventhandler/templates/deployment.yaml b/helm/charts/eventhandler/templates/deployment.yaml new file mode 100644 index 0000000000..2ece25396b --- /dev/null +++ b/helm/charts/eventhandler/templates/deployment.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "global.name" . }} + labels: + {{- include "global.labels" . | nindent 4 }} + app: {{ include "global.name" . }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ include "global.name" . }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + app: {{ include "global.name" . }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- end }} + {{- with .Values.image.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: settings-override + configMap: + name: configuration + containers: + - name: {{ include "global.name" . }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + image: "{{ .Values.image.repository }}:{{- default .Chart.AppVersion .Values.image.tagOverride }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - name: settings-override + mountPath: /app/appsettings.override.json + subPath: appsettings.override.json + readOnly: true + env: + {{- if .Values.global.useBuiltInEventbus }} + - name: infrastructure__eventBus__vendor + value: RabbitMQ + - name: infrastructure__eventBus__connectionInfo + value: "rabbitmq" + - name: infrastructure__eventBus__rabbitMQUsername + value: "admin" + - name: infrastructure__eventBus__rabbitMQPassword + valueFrom: + secretKeyRef: + name: rabbitmq-password + key: "VALUE" + {{- end }} + {{- with .Values.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 9c183737ed..b60b707fce 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -188,6 +188,54 @@ adminui: # unhealthyThreshold - the number of consecutive failed checks required to mark a backend as unhealthy unhealthyThreshold: 2 +eventhandler: + nameOverride: "" + replicas: 1 + maxSurge: 2 + maxUnavailable: 0 + + image: + repository: "ghcr.io/nmshd/backbone-event-handler" + tagOverride: "" + pullPolicy: "IfNotPresent" + imagePullSecrets: [] + + # resources - the resources for the Event Handler container (see https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources) + resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "200m" + memory: "512Mi" + + # securityContext - securityContext for the Consumer API container (see https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-1) + securityContext: {} + + # podSecurityContext - securityContext for the pods deployed by the Deployment (see https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) + podSecurityContext: {} + + podAnnotations: {} + + # env - environment variables for the Event Handler container (see https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # the nodeSelector for the pods deployed by the Deployment (see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) + nodeSelector: {} + + # the tolerations for the pods deployed by the Deployment (see https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) + tolerations: [] + + # the affinity for the pods deployed by the Deployment (see https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity) + affinity: {} + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: "" + admincli: nameOverride: "" diff --git a/scripts/windows/efcore/remove_migration.ps1 b/scripts/windows/efcore/remove_migration.ps1 index e66f646ddb..ac6a305bcf 100644 --- a/scripts/windows/efcore/remove_migration.ps1 +++ b/scripts/windows/efcore/remove_migration.ps1 @@ -3,7 +3,6 @@ Param( [parameter(Mandatory)][ValidateSet("SqlServer", "Postgres", "")] $provider ) -$environment="dbmigrations-" + $provider.ToLower() $repoRoot = git rev-parse --show-toplevel $dbContextName = "${moduleName}DbContext" $adminApiProject = "$repoRoot\AdminApi\src\AdminApi" @@ -16,7 +15,7 @@ function RemoveMigration { switch($moduleName){ "AdminApi" { - New-Item env:"${moduleName}__Infrastructure__SqlDatabase__Provider" -Value $provider -Force | Out-Null + New-Item env:"Infrastructure__SqlDatabase__Provider" -Value $provider -Force | Out-Null $migrationProject = "$repoRoot\AdminApi\src\AdminApi.Infrastructure.Database.$provider" $startupProject = $adminApiProject @@ -29,7 +28,7 @@ function RemoveMigration { } } - $cmd = "dotnet ef migrations remove --startup-project $startupProject --project $migrationProject --context $dbContextName --force -- --environment $environment" + $cmd = "dotnet ef migrations remove --startup-project $startupProject --project $migrationProject --context $dbContextName --force" Write-Host "Executing '$cmd'..." Invoke-Expression $cmd