diff --git a/.azuredevops/pipelines/build-dr-func.yml b/.azuredevops/pipelines/build-dr-func.yml new file mode 100644 index 0000000..669856a --- /dev/null +++ b/.azuredevops/pipelines/build-dr-func.yml @@ -0,0 +1,43 @@ +trigger: + - develop + - main + - releases/* + +pool: + vmImage: windows-latest + +steps: + - task: UseDotNet@2 + displayName: 'Install .NET 6 SDK' + inputs: + packageType: 'sdk' + version: '6.0.x' + performMultiLevelLookup: true + + - script: | + ls + cd Source/CDR.GetDataRecipients + ls + dotnet restore + dotnet build --configuration Release + + - task: DotNetCoreCLI@2 + inputs: + command: publish + arguments: '--configuration Release --output publish_output' + projects: 'Source/CDR.GetDataRecipients/CDR.GetDataRecipients.csproj' + publishWebProjects: false + modifyOutputPath: false + zipAfterPublish: false + + - task: ArchiveFiles@2 + displayName: 'Archive Files' + inputs: + rootFolderOrFile: '$(System.DefaultWorkingDirectory)/publish_output' + includeRootFolder: false + archiveFile: '$(System.DefaultWorkingDirectory)/CDR.GetDataRecipients.zip' + + - task: PublishBuildArtifacts@1 + inputs: + PathToPublish: '$(System.DefaultWorkingDirectory)/CDR.GetDataRecipients.zip' + artifactName: 'functions' diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml index a448f7c..d06f523 100644 --- a/.azuredevops/pipelines/build-v2.yml +++ b/.azuredevops/pipelines/build-v2.yml @@ -9,6 +9,8 @@ resources: trigger: - develop + - main + - releases/* pool: vmImage: ubuntu-latest @@ -24,7 +26,7 @@ steps: displayName: Build mock-register image inputs: command: build - Dockerfile: $(Build.SourcesDirectory)/sb-mock-register/Source/Dockerfile + Dockerfile: $(Build.SourcesDirectory)/sb-mock-register/Source/Dockerfile.for-testing buildContext: $(Build.SourcesDirectory)/sb-mock-register/Source repository: mock-register tags: latest @@ -39,16 +41,6 @@ steps: repository: mock-data-holder-energy tags: latest -# Build mock-data-holder-energy-unit-tests -- task: Docker@2 - displayName: Build mock-data-holder-energy-unit-tests image - inputs: - command: build - Dockerfile: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/Dockerfile.unit-tests - buildContext: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source - repository: mock-data-holder-energy-unit-tests - tags: latest - # Build mock-data-holder-energy-integration-tests - task: Docker@2 displayName: Build mock-data-holder-energy-integration-tests image @@ -59,6 +51,16 @@ steps: repository: mock-data-holder-energy-integration-tests tags: latest +# Build mock-data-holder-energy-for-testing +- task: Docker@2 + displayName: Build mock-data-holder-energy-for-testing image + inputs: + command: build + Dockerfile: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/Dockerfile.for-testing + buildContext: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source + repository: mock-data-holder-energy-for-testing + tags: latest + # List docker images - task: Docker@2 displayName: List Docker images @@ -66,23 +68,6 @@ steps: inputs: command: images -# Run unit tests -- task: DockerCompose@0 - displayName: Unit Tests - Up - inputs: - action: Run a Docker Compose command - dockerComposeFile: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/docker-compose.UnitTests.yml - dockerComposeCommand: up --abort-on-container-exit --exit-code-from mock-data-holder-energy-unit-tests - -# Remove unit tests -- task: DockerCompose@0 - displayName: Unit Tests - Down - condition: always() - inputs: - action: Run a Docker Compose command - dockerComposeFile: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/docker-compose.UnitTests.yml - dockerComposeCommand: down - # Run integration tests - task: DockerCompose@0 displayName: Integration Tests - Up @@ -131,12 +116,6 @@ steps: condition: always() artifact: Mock-Data-Holder-Energy - Logs -# Publish mock-data-holder unit tests results -- publish: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/_temp/mock-data-holder-energy-unit-tests/testresults - displayName: Publish unit tests - condition: always() - artifact: Mock-Data-Holder-Energy - Unit tests - # Publish mock-data-holder integration tests results - publish: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/_temp/mock-data-holder-energy-integration-tests/testresults displayName: Publish integration tests @@ -175,4 +154,18 @@ steps: - publish: $(Build.SourcesDirectory)/sb-mock-data-holder-energy/Source/CDR.DataHolder.Repository/efbundle displayName: Publish EF Migration bundle condition: always() - artifact: Database Migration Scripts \ No newline at end of file + artifact: Database Migration Scripts + +- task: PublishTestResults@2 + displayName: 'Surface Integration Test TRX results to devops' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest + testResultsFiles: '**/results.trx' + #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional + #mergeTestResults: false # Optional + #failTaskOnFailedTests: false # Optional + #testRunTitle: # Optional + #buildPlatform: # Optional + #buildConfiguration: # Optional + #publishRunAttachments: true # Optional \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a7e3e5a..1f731ce 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -71,7 +71,7 @@ jobs: uses: docker/build-push-action@v2 with: context: ./Source - file: ./Source/Dockerfile + file: ./Source/Dockerfile.for-testing platforms: linux/amd64,linux/arm64 push: ${{ github.repository_owner == 'ConsumerDataRight' && github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 041690b..b0e87b2 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -67,7 +67,7 @@ jobs: # Build mock-data-holder-energy image - name: Build the mock-data-holder-energy image run: | - docker build ./mock-data-holder-energy/Source --file ./mock-data-holder-energy/Source/Dockerfile --tag mock-data-holder-energy:latest + docker build ./mock-data-holder-energy/Source --file ./mock-data-holder-energy/Source/Dockerfile.for-testing --tag mock-data-holder-energy:latest # Build mock-data-holder-energy-unit-tests image - name: Build the mock-data-holder-energy-unit-tests-energy image run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 30cf582..0bb3439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0] - 2022-07-22 +### Added +- Azure function to perform Data Recipient discovery by polling the Get Data Recipients API of the Register. + +### Changed +- First version of the Mock Data Holder Energy deployed into the CDR Sandbox. +- Updated Get Energy Concessions schema to match Consumer Data Standards 1.17.0. + ## [0.1.1] - 2022-06-09 ### Changed - Person information in seed data. diff --git a/Help/azurefunctions/HELP.md b/Help/azurefunctions/HELP.md new file mode 100644 index 0000000..60ce81e --- /dev/null +++ b/Help/azurefunctions/HELP.md @@ -0,0 +1,66 @@ +

Azure Functions

+
+The Mock Data Holder Energy solution contains an azure function project.
+The GetDataRecipients function is used to get the list of Data Recipients
+from the Mock Register and update the Mock Data Hoder Energy repository.
+
+ +

To Run and Debug Azure Functions

+
+ The following procedures can be used to run the functions in a local development environment for evaluation of the functions. +
+ +
+1) Start the Mock Register and the Mock Data Holder Energy solutions. +
+ +
+2) Start the Azure Storage Emulator (Azurite): +
+
+ using a MS Windows command prompt:
+
+ +``` +md C:\azurite +cd "C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator" +azurite --silent --location c:\azurite --debug c:\azurite\debug.log +``` + +
+ Noting this is only required to be performed once, it will then be listening on ports - 10000, 10001 and 10002
+ when debugging is started from MS Visual Studio by selecting CDR.GetDataRecipients as the startup project
+ (by starting a debug instance using F5 or Debug > Start Debugging) +
+
+
+ or by using a MS Windows command prompt:
+
+ +``` +navigate to .\mock-data-holder-energy\Source\CDR.GetDataRecipients
+func start --verbose
+``` + +

3) Open the Mock Data Holder Energy in MS Visual Studio, select CDR.GetDataRecipients as the startup project.

+ +

4) Start each debug instances (F5 or Debug > Start Debugging), this will simulate the discovery of Data Recipients and the

+
+ updating of the data in the Mock Data Holder Energy repositories. +
+ +
+ Noting the below sql scripts are used to observe the results.
+
+ +``` +SELECT * FROM [cdr-mdhe].[dbo].[LegalEntity] +SELECT * FROM [cdr-mdhe].[dbo].[Brand] +SELECT * FROM [cdr-mdhe].[dbo].[SoftwareProduct] +SELECT * FROM [cdr-mdhe].[dbo].[LogEvents_DRService] +``` + +

To Build Azure Functions

+
+ dotnet SDK 6.0.30x or higher is required. Latest SDK can be found from the link https://microsoft.com/net +
\ No newline at end of file diff --git a/README.md b/README.md index 8f814ad..ac3c984 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Consumer Data Right Logo](https://raw.githubusercontent.com/ConsumerDataRight/mock-data-holder-energy/main/cdr-logo.png) -[![Consumer Data Standards v1.16.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.16.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.16.0/#introduction) +[![Consumer Data Standards v1.17.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.17.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards/#introduction) [![FAPI 1.0 Advanced Profile](https://img.shields.io/badge/FAPI%201.0-orange.svg)](https://openid.net/specs/openid-financial-api-part-2-1_0.html) [![made-with-dotnet](https://img.shields.io/badge/Made%20with-.NET-1f425Ff.svg)](https://dotnet.microsoft.com/) [![made-with-csharp](https://img.shields.io/badge/Made%20with-C%23-1f425Ff.svg)](https://docs.microsoft.com/en-us/dotnet/csharp/) @@ -13,7 +13,7 @@ This project includes source code, documentation and instructions for a Consumer This repository contains a mock implementation of a Mock Data Holder Energy and is offered to help the community in the development and testing of their CDR solutions. ## Mock Data Holder Energy - Alignment -The Mock Data Holder Energy aligns to [v1.16.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.16.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards/#introduction). +The Mock Data Holder Energy aligns to [v1.17.0](https://consumerdatastandardsaustralia.github.io/standards/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards/#introduction). The Mock Data Holder Energy is based on the [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder) codebase. The [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder) has passed v3.2 of the [Conformance Test Suite for Data Holders](https://www.cdr.gov.au/for-providers/conformance-test-suite-data-holders). Conformance Testing specific to the Mock Data Holder Energy has not yet been completed. The Mock Data Holder Energy is compliant with the [FAPI 1.0 Advanced Profile](https://openid.net/specs/openid-financial-api-part-2-1_0.html). The Mock Data Holder Energy aligns to [FAPI 1.0 Migration Phase 2](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.16.0/#authentication-flows). @@ -115,6 +115,9 @@ The following diagram outlines the high level architecture of the Mock Data Hold [Mock Data Holder Energy - Architecture](https://raw.githubusercontent.com/ConsumerDataRight/mock-data-holder-energy/main/mock-data-holder-energy-architecture.png) +Get Data Recipients discovery Azure Function: + +[Get Data Recipients discovery function](mock-data-holder-discovery-architecture.png) ## Mock Data Holder Energy - Components The Mock Data Holder Energy contains the following components: @@ -140,6 +143,9 @@ The Mock Data Holder Energy contains the following components: - Not part of the Consumer Data Standards, but allows for the maintenance of data in the Mock Data Holder Energy repository. - Also includes trigger points to refresh the Data Recipient, Data Recipient Status and Software Product Status from the Mock Register. - A user interface may be added at some time in the future to provide user friendly access to the repository data. +- Azure Function + - An Azure Function that can automate the continuous Get Data Recipients discovery process. + - To get help on the Azure Functions, see the [help guide](./Help/azurefunctions/HELP.md). - Repository - A SQL database containing Mock Data Holder Energy data. diff --git a/Source/CDR.DataHolder.API.Gateway.mTLS/CDR.DataHolder.API.Gateway.mTLS.csproj b/Source/CDR.DataHolder.API.Gateway.mTLS/CDR.DataHolder.API.Gateway.mTLS.csproj index 5692fc6..9bd7715 100644 --- a/Source/CDR.DataHolder.API.Gateway.mTLS/CDR.DataHolder.API.Gateway.mTLS.csproj +++ b/Source/CDR.DataHolder.API.Gateway.mTLS/CDR.DataHolder.API.Gateway.mTLS.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 @@ -35,6 +35,10 @@ + + + + Always diff --git a/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/CertificateValidator.cs b/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/CertificateValidator.cs index d649038..9b27126 100644 --- a/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/CertificateValidator.cs +++ b/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/CertificateValidator.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Security.Cryptography.X509Certificates; +using CDR.DataHolder.API.Infrastructure.Exceptions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -11,7 +12,6 @@ namespace CDR.DataHolder.API.Gateway.mTLS.Certificates /// public class CertificateValidator : ICertificateValidator { - private const string ROOT_CA_SUBJECT = "CN=Mock CDR CA, OU=CDR, O=ACCC, L=Canberra, S=ACT, C=AU"; private readonly ILogger _logger; private readonly IConfiguration _config; @@ -21,7 +21,7 @@ public CertificateValidator(ILogger logger, IConfiguration _config = config; } - public bool IsValid(X509Certificate2 clientCert) + public void ValidateClientCertificate(X509Certificate2 clientCert) { _logger.LogInformation($"Validating certificate within the {nameof(CertificateValidator)}"); @@ -34,7 +34,8 @@ public bool IsValid(X509Certificate2 clientCert) var rootCACertificate = new X509Certificate2(_config.GetValue("RootCACertificate:Path")); var ch = new X509Chain(); ch.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - ch.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags; + ch.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; + ch.ChainPolicy.CustomTrustStore.Clear(); ch.ChainPolicy.CustomTrustStore.Add(rootCACertificate); ch.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; @@ -44,16 +45,13 @@ public bool IsValid(X509Certificate2 clientCert) } catch (Exception ex) { - throw new ArgumentException("The certificate chain cannot be discovered from the provided client certificate.", ex); + throw new ClientCertificateException("The certificate chain cannot be discovered from the provided client certificate.", ex); } if (ch.ChainStatus.Any()) { - _logger.LogError("The client cert could not be verified to have been issued by '{subject}'. {statusInformation}", ROOT_CA_SUBJECT, ch.ChainStatus[0].StatusInformation); - return false; + throw new ClientCertificateException(ch.ChainStatus.First().StatusInformation); } - - return true; } } } diff --git a/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/ICertificateValidator.cs b/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/ICertificateValidator.cs index 88ab597..1b57d43 100644 --- a/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/ICertificateValidator.cs +++ b/Source/CDR.DataHolder.API.Gateway.mTLS/Certificates/ICertificateValidator.cs @@ -4,6 +4,6 @@ namespace CDR.DataHolder.API.Gateway.mTLS.Certificates { public interface ICertificateValidator { - bool IsValid(X509Certificate2 clientCert); + void ValidateClientCertificate(X509Certificate2 clientCert); } } diff --git a/Source/CDR.DataHolder.API.Gateway.mTLS/Startup.cs b/Source/CDR.DataHolder.API.Gateway.mTLS/Startup.cs index fd10a4e..083dda8 100644 --- a/Source/CDR.DataHolder.API.Gateway.mTLS/Startup.cs +++ b/Source/CDR.DataHolder.API.Gateway.mTLS/Startup.cs @@ -1,9 +1,12 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using CDR.DataHolder.API.Gateway.mTLS.Certificates; +using CDR.DataHolder.API.Infrastructure.Exceptions; using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -11,6 +14,7 @@ using Ocelot.DependencyInjection; using Ocelot.Middleware; using Serilog; +using static System.Net.Mime.MediaTypeNames; namespace CDR.DataHolder.API.Gateway.mTLS { @@ -44,39 +48,55 @@ public void ConfigureServices(IServiceCollection services) logger.LogInformation("OnCertificateValidated..."); var certValidator = context.HttpContext.RequestServices.GetService(); - if (!certValidator.IsValid(context.ClientCertificate)) - { - const string failValidationMsg = "The client certificate failed to validate"; - logger.LogWarning(failValidationMsg); - context.Fail(failValidationMsg); - } - + certValidator.ValidateClientCertificate(context.ClientCertificate); + context.Success(); return Task.CompletedTask; }, OnAuthenticationFailed = context => { - context.Fail("invalid cert"); - return Task.CompletedTask; + context.Fail("invalid client certificate"); + throw context.Exception; } }; }) // Adding an ICertificateValidationCache results in certificate auth caching the results. // The default implementation uses a memory cache. .AddCertificateCache(); + services.AddAuthorization(); services.AddOcelot(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) + app.UseSerilogRequestLogging(); + + app.UseExceptionHandler(exceptionHandlerApp => { - app.UseDeveloperExceptionPage(); - } + exceptionHandlerApp.Run(async context => + { + // Try and retrieve the error from the ExceptionHandler middleware + var exceptionDetails = context.Features.Get(); + var ex = exceptionDetails?.Error; - app.UseSerilogRequestLogging(); + if (ex is ClientCertificateException) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + } + else + { + context.Response.StatusCode = StatusCodes.Status502BadGateway; + } + + // using static System.Net.Mime.MediaTypeNames; + context.Response.ContentType = Text.Plain; + await context.Response.WriteAsync($"An error occurred handling the request: {ex?.Message}"); + }); + }); app.UseHttpsRedirection(); + app.UseAuthentication(); + app.UseAuthorization(); var pipelineConfiguration = new OcelotPipelineConfiguration { @@ -97,8 +117,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) await next.Invoke(); } }; - - app.UseAuthentication(); app.UseOcelot(pipelineConfiguration).Wait(); } } diff --git a/Source/CDR.DataHolder.API.Infrastructure.UnitTests/CDR.DataHolder.API.Infrastructure.UnitTests.csproj b/Source/CDR.DataHolder.API.Infrastructure.UnitTests/CDR.DataHolder.API.Infrastructure.UnitTests.csproj index 478d9c1..bea42b3 100644 --- a/Source/CDR.DataHolder.API.Infrastructure.UnitTests/CDR.DataHolder.API.Infrastructure.UnitTests.csproj +++ b/Source/CDR.DataHolder.API.Infrastructure.UnitTests/CDR.DataHolder.API.Infrastructure.UnitTests.csproj @@ -3,9 +3,9 @@ net6.0 false - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.API.Infrastructure/CDR.DataHolder.API.Infrastructure.csproj b/Source/CDR.DataHolder.API.Infrastructure/CDR.DataHolder.API.Infrastructure.csproj index 84383fd..32f9b53 100644 --- a/Source/CDR.DataHolder.API.Infrastructure/CDR.DataHolder.API.Infrastructure.csproj +++ b/Source/CDR.DataHolder.API.Infrastructure/CDR.DataHolder.API.Infrastructure.csproj @@ -2,9 +2,9 @@ net6.0 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.API.Infrastructure/Constants.cs b/Source/CDR.DataHolder.API.Infrastructure/Constants.cs index 18b1b63..9112485 100644 --- a/Source/CDR.DataHolder.API.Infrastructure/Constants.cs +++ b/Source/CDR.DataHolder.API.Infrastructure/Constants.cs @@ -14,11 +14,19 @@ public static class ApiScopes public static class Energy { public const string AccountsBasicRead = "energy:accounts.basic:read"; + public const string AccountsDetailRead = "energy:accounts.detail:read"; public const string ConcessionsRead = "energy:accounts.concessions:read"; + public const string ServicePointsBasicRead = "energy:electricity.servicepoints.basic:read"; + public const string ServicePointsDetailRead = "energy:electricity.servicepoints.detail:read"; + public const string ElectricityUsageRead = "energy:electricity.usage:read"; + public const string ElectricityDerRead = "energy:electricity.der:read"; + public const string EnergyAccountsPaymentScheduleRead = "energy:accounts.paymentschedule:read"; + public const string EnergyBillingRead = "energy:billing:read"; } public static class Common { public const string CustomerBasicRead = "common:customer.basic:read"; + public const string CustomerDetailRead = "common:customer.detail:read"; } } diff --git a/Source/CDR.DataHolder.API.Infrastructure/Exceptions/ClientCertificateException.cs b/Source/CDR.DataHolder.API.Infrastructure/Exceptions/ClientCertificateException.cs new file mode 100644 index 0000000..cdbe35c --- /dev/null +++ b/Source/CDR.DataHolder.API.Infrastructure/Exceptions/ClientCertificateException.cs @@ -0,0 +1,15 @@ +using System; + +namespace CDR.DataHolder.API.Infrastructure.Exceptions +{ + public class ClientCertificateException : Exception + { + public ClientCertificateException(string message) : base($"An error occurred validating the client certificate: {message}") + { + } + + public ClientCertificateException(string message, Exception ex) : base($"An error occurred validating the client certificate: {message}", ex) + { + } + } +} diff --git a/Source/CDR.DataHolder.Admin.API/CDR.DataHolder.Admin.API.csproj b/Source/CDR.DataHolder.Admin.API/CDR.DataHolder.Admin.API.csproj index 9f30f83..48db728 100644 --- a/Source/CDR.DataHolder.Admin.API/CDR.DataHolder.Admin.API.csproj +++ b/Source/CDR.DataHolder.Admin.API/CDR.DataHolder.Admin.API.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Domain/CDR.DataHolder.Domain.csproj b/Source/CDR.DataHolder.Domain/CDR.DataHolder.Domain.csproj index 6c98340..3bd403b 100644 --- a/Source/CDR.DataHolder.Domain/CDR.DataHolder.Domain.csproj +++ b/Source/CDR.DataHolder.Domain/CDR.DataHolder.Domain.csproj @@ -2,9 +2,9 @@ net6.0 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Domain/Entities/EnergyAccountConcession.cs b/Source/CDR.DataHolder.Domain/Entities/EnergyAccountConcession.cs index b3e574e..becf931 100644 --- a/Source/CDR.DataHolder.Domain/Entities/EnergyAccountConcession.cs +++ b/Source/CDR.DataHolder.Domain/Entities/EnergyAccountConcession.cs @@ -2,14 +2,15 @@ { public class EnergyAccountConcession { + public string Type { get; set; } public string DisplayName { get; set; } public string AdditionalInfo { get; set; } public string AdditionalInfoUri { get; set; } public string StartDate { get; set; } public string EndDate { get; set; } - public string DailyDiscount { get; set; } - public string MonthlyDiscount { get; set; } - public string YearlyDiscount { get; set; } - public string PercentageDiscount { get; set; } + public string DiscountFrequency { get; set; } + public string Amount { get; set; } + public string Percentage { get; set; } + public string[] AppliedTo { get; set; } } } \ No newline at end of file diff --git a/Source/CDR.DataHolder.IdentityServer.UnitTests/CDR.DataHolder.IdentityServer.UnitTests.csproj b/Source/CDR.DataHolder.IdentityServer.UnitTests/CDR.DataHolder.IdentityServer.UnitTests.csproj index 8f3e312..82181f8 100644 --- a/Source/CDR.DataHolder.IdentityServer.UnitTests/CDR.DataHolder.IdentityServer.UnitTests.csproj +++ b/Source/CDR.DataHolder.IdentityServer.UnitTests/CDR.DataHolder.IdentityServer.UnitTests.csproj @@ -3,9 +3,9 @@ net6.0 false - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.IdentityServer/AutoMapper/RegistrationProfile.cs b/Source/CDR.DataHolder.IdentityServer/AutoMapper/RegistrationProfile.cs index 3635bff..24bc81c 100644 --- a/Source/CDR.DataHolder.IdentityServer/AutoMapper/RegistrationProfile.cs +++ b/Source/CDR.DataHolder.IdentityServer/AutoMapper/RegistrationProfile.cs @@ -8,6 +8,7 @@ using IdentityServer4.Models; using static CDR.DataHolder.IdentityServer.CdsConstants; using CDR.DataHolder.IdentityServer.Extensions; +using static CDR.DataHolder.API.Infrastructure.Constants; namespace CDR.DataHolder.IdentityServer.AutoMapper { diff --git a/Source/CDR.DataHolder.IdentityServer/CDR.DataHolder.IdentityServer.csproj b/Source/CDR.DataHolder.IdentityServer/CDR.DataHolder.IdentityServer.csproj index 12b355f..81a4f98 100644 --- a/Source/CDR.DataHolder.IdentityServer/CDR.DataHolder.IdentityServer.csproj +++ b/Source/CDR.DataHolder.IdentityServer/CDR.DataHolder.IdentityServer.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.IdentityServer/CdsConstants.cs b/Source/CDR.DataHolder.IdentityServer/CdsConstants.cs index 8a95450..c1d1242 100644 --- a/Source/CDR.DataHolder.IdentityServer/CdsConstants.cs +++ b/Source/CDR.DataHolder.IdentityServer/CdsConstants.cs @@ -714,37 +714,6 @@ public static class JwtToken public const string JwtType = "JWT"; } - public static class ApiScopes - { - public static class Banking - { - public const string Accounts = "bank:accounts.basic:read"; - public const string Transactions = "bank:transactions:read"; - } - } - - public static class StandardScopes - { - // - // Summary: - // REQUIRED. Informs the Authorization Server that the Client is making an OpenID - // Connect request. If the openid scope value is not present, the behavior is entirely - // unspecified. - public const string OpenId = "openid"; - // - // Summary: - // OPTIONAL. This scope value requests access to the End-User's default profile - // Claims, which are: name, family_name, given_name, middle_name, nickname, preferred_username, - // profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. - public const string Profile = "profile"; - // - // Summary: - // This scope value MUST NOT be used with the OpenID Connect Implicit Client Implementer's - // Guide 1.0. See the OpenID Connect Basic Client Implementer's Guide 1.0 (http://openid.net/specs/openid-connect-implicit-1_0.html#OpenID.Basic) - // for its usage in that subset of OpenID Connect. - public const string OfflineAccess = "offline_access"; - } - public static class ProtocolTypes { public const string OpenIdConnect = "oidc"; diff --git a/Source/CDR.DataHolder.IdentityServer/Configuration/InMemoryConfig.cs b/Source/CDR.DataHolder.IdentityServer/Configuration/InMemoryConfig.cs index 395d376..f498571 100644 --- a/Source/CDR.DataHolder.IdentityServer/Configuration/InMemoryConfig.cs +++ b/Source/CDR.DataHolder.IdentityServer/Configuration/InMemoryConfig.cs @@ -1,41 +1,40 @@ using System.Collections.Generic; using IdentityModel; using IdentityServer4.Models; +using Microsoft.Extensions.Configuration; namespace CDR.DataHolder.IdentityServer.Configuration { public static class InMemoryConfig { - public static IEnumerable Apis => + public static IEnumerable Apis(IConfiguration config) => new List { - new ApiResource("cds-au", "Mock Data Holder (MDH) Resource API") + new ApiResource("cds-au", "Mock Data Holder Energy (MDHE) Resource API") { - // Need to add all the scopes supported by cds-au api. - // All the scopes supported by the cds-au API. - Scopes = new string[] { - API.Infrastructure.Constants.CdrScopes.Registration, - API.Infrastructure.Constants.ApiScopes.Energy.AccountsBasicRead, - API.Infrastructure.Constants.ApiScopes.Energy.ConcessionsRead, - API.Infrastructure.Constants.ApiScopes.Common.CustomerBasicRead, - - } + Scopes = config["ScopesSupported"].Split(',') } }; - public static IEnumerable ApiScopes => - new[] + public static IEnumerable ApiScopes(IConfiguration config) + { + var apiScopes = new List(); + + // These are the supported scopes for this data holder implementation. + foreach (var scope in config["ScopesSupported"].Split(',')) { - // These are the supported scopes for this data holder implementation. - new ApiScope(API.Infrastructure.Constants.CdrScopes.Registration, "Dynamic Client Registration (DCR)"), - new ApiScope(API.Infrastructure.Constants.ApiScopes.Energy.AccountsBasicRead, "Basic read access to energy accounts"), - new ApiScope(API.Infrastructure.Constants.ApiScopes.Energy.ConcessionsRead, "The scope would allow the third party to access the details of any concessions for a customer’s energy account."), - new ApiScope(API.Infrastructure.Constants.ApiScopes.Common.CustomerBasicRead, "Basic read access to customer information"), + if (scope != API.Infrastructure.Constants.StandardScopes.OpenId && scope != API.Infrastructure.Constants.StandardScopes.Profile) + { + apiScopes.Add(new ApiScope(scope)); + } + } - // These are the additional scopes for CDR. These need to be here to allow a DR with more scopes than supported to authorise. - new ApiScope(API.Infrastructure.Constants.CdrScopes.MetricsBasicRead, "Metrics data accessible ONLY to the CDR Register"), - new ApiScope(API.Infrastructure.Constants.CdrScopes.MetadataUpdate, "Update notification accessible ONLY to the CDR Register"), - }; + // These are the additional scopes for CDR. These need to be here to allow a DR with more scopes than supported to authorise. + apiScopes.Add(new ApiScope(API.Infrastructure.Constants.CdrScopes.MetricsBasicRead, "Metrics data accessible ONLY to the CDR Register")); + apiScopes.Add(new ApiScope(API.Infrastructure.Constants.CdrScopes.MetadataUpdate, "Update notification accessible ONLY to the CDR Register")); + + return apiScopes; + } public static IEnumerable IdentityResources => new List diff --git a/Source/CDR.DataHolder.IdentityServer/Program.cs b/Source/CDR.DataHolder.IdentityServer/Program.cs index e2aa4f9..3395610 100644 --- a/Source/CDR.DataHolder.IdentityServer/Program.cs +++ b/Source/CDR.DataHolder.IdentityServer/Program.cs @@ -44,8 +44,16 @@ public static int Main(string[] args) CreateHostBuilder(args, configuration, new SerilogLoggerFactory(Log.Logger).CreateLogger()).Build().Run(); return 0; } - catch (Exception ex) when (ex is not OperationCanceledException && ex.GetType().Name != "StopTheHostException") + catch (Exception ex) { + // StopTheHostException - Unhandled exception from a BackgroundService thrown out of + // HostBuilder.Build() on adding a migration to a EF/.NET Core 6.0 RC2 project, needs to be dealt with gracefully. + string type = ex.GetType().Name; + if (type.Equals("StopTheHostException", StringComparison.Ordinal)) + { + throw; + } + Log.Fatal(ex, "Host terminated unexpectedly"); return 1; } diff --git a/Source/CDR.DataHolder.IdentityServer/Services/ClientArrangementRevocationEndpointRequestService.cs b/Source/CDR.DataHolder.IdentityServer/Services/ClientArrangementRevocationEndpointRequestService.cs index 803fac7..bec8424 100644 --- a/Source/CDR.DataHolder.IdentityServer/Services/ClientArrangementRevocationEndpointRequestService.cs +++ b/Source/CDR.DataHolder.IdentityServer/Services/ClientArrangementRevocationEndpointRequestService.cs @@ -1,11 +1,9 @@ -using CDR.DataHolder.IdentityServer.Configuration; -using CDR.DataHolder.IdentityServer.Interfaces; +using CDR.DataHolder.IdentityServer.Interfaces; using CDR.DataHolder.IdentityServer.Models; using CDR.DataHolder.IdentityServer.Services.Interfaces; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Serilog.Context; using System; @@ -115,7 +113,7 @@ public async Task SendRevocationRequest(string cd // Call the DR's arrangement revocation endpoint. var httpResponse = await _clientArrangementRevocationEndpointHttpClient.PostToArrangementRevocationEndPoint( - (await GetFormValues(cdrArrangementId, useJwt)), + (await GetFormValues(cdrArrangementId, brandId, revocationUri.ToString(), useJwt)), signedBearerTokenJwt, revocationUri); @@ -150,13 +148,25 @@ public async Task GetSignedJwt(JwtSecurityToken jwtSecurityToken) return $"{plaintext}.{signature}"; } - private async Task> GetFormValues(string cdrArrangementId, bool useJwt = false) + private async Task> GetFormValues( + string cdrArrangementId, + string brandId, + string audience, + bool useJwt = false) { var formValues = new Dictionary(); if (useJwt) { - var jwt = new JwtSecurityToken(claims: new Claim[] { new Claim(CdrArrangementRevocationRequest.CdrArrangementJwt, cdrArrangementId) }); + var jwt = new JwtSecurityToken( + issuer: brandId, + audience: audience, + expires: DateTime.UtcNow.AddMinutes(_defaultExpiryMinutes), + claims: new Claim[] { + new Claim(CdrArrangementRevocationRequest.CdrArrangementId, cdrArrangementId), + new Claim(JwtRegisteredClaimNames.Sub, brandId), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }); formValues.Add(CdrArrangementRevocationRequest.CdrArrangementJwt, (await GetSignedJwt(jwt))); } else @@ -164,6 +174,8 @@ private async Task> GetFormValues(string cdrArrangeme formValues.Add(CdrArrangementRevocationRequest.CdrArrangementId, cdrArrangementId); } + _logger.LogInformation("Arrangement revocation request using {form}:{form_value} ", formValues.First().Key, formValues.First().Value); + return formValues; } } diff --git a/Source/CDR.DataHolder.IdentityServer/Services/ClientService.cs b/Source/CDR.DataHolder.IdentityServer/Services/ClientService.cs index a66e72e..615fdb6 100644 --- a/Source/CDR.DataHolder.IdentityServer/Services/ClientService.cs +++ b/Source/CDR.DataHolder.IdentityServer/Services/ClientService.cs @@ -1,8 +1,13 @@ -using System.Threading.Tasks; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Threading.Tasks; using CDR.DataHolder.IdentityServer.Configuration; using CDR.DataHolder.IdentityServer.Models; using CDR.DataHolder.IdentityServer.Stores; using IdentityServer4.Models; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; namespace CDR.DataHolder.IdentityServer.Services { @@ -10,9 +15,14 @@ public class ClientService : IClientService { private readonly DynamicClientStore _clientStore; private readonly IConfigurationSettings _configurationSettings; + private readonly ILogger _logger; - public ClientService(DynamicClientStore clientStore, IConfigurationSettings configurationSettings) + public ClientService( + ILogger logger, + DynamicClientStore clientStore, + IConfigurationSettings configurationSettings) { + _logger = logger; _configurationSettings = configurationSettings; _clientStore = clientStore; } @@ -40,5 +50,27 @@ public async Task UpdateClient(DataRecipientClient client) return await _clientStore.StoreClientAsync(client); } + + public async Task RefreshJwks(string clientId) + => await _clientStore.RefreshJwks(clientId); + + public async Task EnsureKid(string clientId, string jwt, TokenValidationParameters tokenValidationParameters) + { + // Read the token without validation, to retrieve the kid from the header. + var handler = new JwtSecurityTokenHandler(); + var unvalidatedToken = handler.ReadJwtToken(jwt); + + // Check if the incoming jwt has a key id that is including in the client's secret keys. + if (unvalidatedToken.Header.Kid != null + && !tokenValidationParameters.IssuerSigningKeys.Any(k => k.KeyId.Equals(unvalidatedToken.Header.Kid, StringComparison.OrdinalIgnoreCase))) + { + _logger.LogInformation("Matching kid ({kid}) not found in client secrets, refreshing jwks from client...", unvalidatedToken.Header.Kid); + + // Is there a matching key id between the clientAssertion JWT and the JWKS stored for the client? + // If not, reload the JWKS from the client. + var refreshedClient = await RefreshJwks(clientId); + tokenValidationParameters.IssuerSigningKeys = await refreshedClient.ClientSecrets.GetKeysAsync(); + } + } } } \ No newline at end of file diff --git a/Source/CDR.DataHolder.IdentityServer/Services/IClientService.cs b/Source/CDR.DataHolder.IdentityServer/Services/IClientService.cs index eed3b32..a09dcdf 100644 --- a/Source/CDR.DataHolder.IdentityServer/Services/IClientService.cs +++ b/Source/CDR.DataHolder.IdentityServer/Services/IClientService.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using CDR.DataHolder.IdentityServer.Models; using IdentityServer4.Models; +using Microsoft.IdentityModel.Tokens; namespace CDR.DataHolder.IdentityServer.Services { @@ -15,5 +16,9 @@ public interface IClientService Task RegisterClient(DataRecipientClient client); Task UpdateClient(DataRecipientClient client); + + Task RefreshJwks(string clientId); + + Task EnsureKid(string clientId, string jwt, TokenValidationParameters tokenValidationParameters); } } diff --git a/Source/CDR.DataHolder.IdentityServer/Services/TokenResponseGenerator.cs b/Source/CDR.DataHolder.IdentityServer/Services/TokenResponseGenerator.cs index 9864917..c3ce39d 100644 --- a/Source/CDR.DataHolder.IdentityServer/Services/TokenResponseGenerator.cs +++ b/Source/CDR.DataHolder.IdentityServer/Services/TokenResponseGenerator.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using static CDR.DataHolder.API.Infrastructure.Constants; using static CDR.DataHolder.IdentityServer.CdsConstants; namespace CDR.DataHolder.IdentityServer.Services diff --git a/Source/CDR.DataHolder.IdentityServer/Startup.cs b/Source/CDR.DataHolder.IdentityServer/Startup.cs index 76308eb..c1f153a 100644 --- a/Source/CDR.DataHolder.IdentityServer/Startup.cs +++ b/Source/CDR.DataHolder.IdentityServer/Startup.cs @@ -132,8 +132,8 @@ public void ConfigureServices(IServiceCollection services) options.ConfigureDbContext = b => b.UseSqlServer(migrationsConnectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddInMemoryIdentityResources(InMemoryConfig.IdentityResources) - .AddInMemoryApiResources(InMemoryConfig.Apis) - .AddInMemoryApiScopes(InMemoryConfig.ApiScopes) + .AddInMemoryApiResources(InMemoryConfig.Apis(_configuration)) + .AddInMemoryApiScopes(InMemoryConfig.ApiScopes(_configuration)) .AddProfileService(); services.AddDbContext(options => options.UseSqlServer(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Identity.Default))); diff --git a/Source/CDR.DataHolder.IdentityServer/Stores/ClientStore.cs b/Source/CDR.DataHolder.IdentityServer/Stores/ClientStore.cs index bbca356..3f8b461 100644 --- a/Source/CDR.DataHolder.IdentityServer/Stores/ClientStore.cs +++ b/Source/CDR.DataHolder.IdentityServer/Stores/ClientStore.cs @@ -10,6 +10,7 @@ using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using static CDR.DataHolder.IdentityServer.CdsConstants; @@ -20,19 +21,23 @@ public class ClientStore : IClientStore { protected readonly ILogger _logger; protected readonly ConfigurationDbContext _configurationDbContext; + protected readonly IDistributedCache _cache; - public ClientStore(ILogger logger, - ConfigurationDbContext configurationDbContext) + public ClientStore( + ILogger logger, + ConfigurationDbContext configurationDbContext, + IDistributedCache cache) { _logger = logger; _configurationDbContext = configurationDbContext; + _cache = cache; } public async Task FindClientByIdAsync(string clientId) { - _logger.LogInformation("DynamicClientStore FindClientByIdAsync"); + _logger.LogInformation($"{nameof(ClientStore)}.{nameof(FindClientByIdAsync)}"); - var client = await _configurationDbContext.Clients + var client = await _configurationDbContext.Clients.AsNoTracking() .Include(c => c.RedirectUris) .Include(c => c.ClientSecrets) .Include(c => c.AllowedGrantTypes) @@ -46,27 +51,61 @@ public ClientStore(ILogger logger, return null; } - // This implements the client key rotation - // Get the latest JWKs from the client secrets. The keys may have been rotated from the client side, so it is always best to get the latest keys dynamically. - // Filter only URIs as there can be other secrets such as keys. + return await GetClientSecrets(client, true); + } + + public async Task RefreshJwks(string clientId) + { + _logger.LogInformation($"{nameof(ClientStore)}.{nameof(RefreshJwks)}"); + + var client = await _configurationDbContext.Clients.AsNoTracking() + .Include(c => c.RedirectUris) + .Include(c => c.ClientSecrets) + .Include(c => c.AllowedGrantTypes) + .Include(c => c.AllowedScopes) + .Include(c => c.Claims) + .FirstOrDefaultAsync(x => x.ClientId == clientId); + + if (client == null) + { + _logger.LogError("Client {ClientId} is not found.", clientId); + return null; + } + + return await GetClientSecrets(client, false); + } + + private async Task GetClientSecrets(IdentityServer4.EntityFramework.Entities.Client client, bool checkCache = true) + { + _logger.LogInformation($"{nameof(ClientStore)}.{nameof(GetClientSecrets)}"); + _logger.LogInformation("Client secrets: {secretCount}", client.ClientSecrets.Count); + var updatedClientSecrets = new List(); foreach (var clientSecret in client.ClientSecrets) { + _logger.LogDebug("Client ID: {clientId}. Client secret type: {type}. Client secret value: {value}. Is url: {isUrl}", client.ClientId, clientSecret.Type, clientSecret.Value, Uri.IsWellFormedUriString(clientSecret.Value, UriKind.Absolute)); + if (clientSecret.Type == SecretTypes.JsonWebKey && Uri.IsWellFormedUriString(clientSecret.Value, UriKind.Absolute)) { try { - var jwks = await GetJwks(clientSecret.Value); + _logger.LogInformation("Retrieving JWKS from {secretCount}. Check cache: {checkCache}", clientSecret.Value, checkCache); + + var jwks = await GetJwks(clientSecret.Value, checkCache); updatedClientSecrets.AddRange( jwks.Keys.Select(key => ConvertJwkToClientSecret(key))); } catch (Exception ex) { _logger.LogError(ex, "Failed to get JWKs from JwksUri endpoint {jwksUri}", clientSecret.Value); + + // Failed to retrieve the client secret jwks, so just re-use the old one. + updatedClientSecrets.Add(clientSecret); } } else { + _logger.LogInformation("A non JWK secret found: {type}", clientSecret.Type); updatedClientSecrets.Add(clientSecret); } } @@ -89,10 +128,22 @@ private static ClientSecret ConvertJwkToClientSecret(IdentityModel.Jwk.JsonWebKe /// Note: /// This can be cached for a short period of time for performance enhancement /// - private async Task GetJwks(string jwksEndpoint) + private async Task GetJwks(string jwksEndpoint, bool checkCache = true) { _logger.LogInformation($"{nameof(ClientStore)}.{nameof(GetJwks)}"); + if (checkCache) + { + // Checking jwks is in cache. + var item = await _cache.GetStringAsync(jwksEndpoint); + if (!string.IsNullOrEmpty(item)) + { + _logger.LogInformation("Cache hit: {jwksUri}", jwksEndpoint); + _logger.LogDebug("Cache hit contents: {item}", item); + return new JsonWebKeySet(item); + } + } + var clientHandler = new HttpClientHandler(); clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; @@ -104,6 +155,9 @@ private async Task GetJwks(string jwksEndpoint) _logger.LogDebug("JWKS: {jwks}", jwks); + _logger.LogDebug("Adding {jwksUri} to cache...", jwksEndpoint); + _cache.SetString(jwksEndpoint, jwks); + return new JsonWebKeySet(jwks); } } diff --git a/Source/CDR.DataHolder.IdentityServer/Stores/DynamicClientStore.cs b/Source/CDR.DataHolder.IdentityServer/Stores/DynamicClientStore.cs index cede7f8..ad65779 100644 --- a/Source/CDR.DataHolder.IdentityServer/Stores/DynamicClientStore.cs +++ b/Source/CDR.DataHolder.IdentityServer/Stores/DynamicClientStore.cs @@ -6,6 +6,7 @@ using IdentityServer4.EntityFramework.Mappers; using IdentityServer4.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using static CDR.DataHolder.IdentityServer.CdsConstants; @@ -13,7 +14,10 @@ namespace CDR.DataHolder.IdentityServer.Stores { public class DynamicClientStore : ClientStore { - public DynamicClientStore(ILogger logger, ConfigurationDbContext configurationDbContext):base(logger, configurationDbContext) + public DynamicClientStore( + ILogger logger, + ConfigurationDbContext configurationDbContext, + IDistributedCache cache) :base(logger, configurationDbContext, cache) { } diff --git a/Source/CDR.DataHolder.IdentityServer/Validation/ClientDetailsValidator.cs b/Source/CDR.DataHolder.IdentityServer/Validation/ClientDetailsValidator.cs index b0a2337..ec589b2 100644 --- a/Source/CDR.DataHolder.IdentityServer/Validation/ClientDetailsValidator.cs +++ b/Source/CDR.DataHolder.IdentityServer/Validation/ClientDetailsValidator.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Threading.Tasks; using CDR.DataHolder.API.Infrastructure.Extensions; using CDR.DataHolder.IdentityServer.Events; using CDR.DataHolder.IdentityServer.Extensions; using CDR.DataHolder.IdentityServer.Models; +using CDR.DataHolder.IdentityServer.Services; using FluentValidation; using FluentValidation.Validators; using IdentityServer4.Configuration; @@ -26,17 +28,20 @@ public class ClientDetailsValidator : AbstractValidator private readonly IEventService _eventService; private readonly ILogger _logger; private readonly ITokenReplayCache _tokenCache; + private readonly IClientService _clientService; public ClientDetailsValidator( IConfiguration config, IdentityServerOptions options, IEventService eventService, + IClientService clientService, ILogger logger, ITokenReplayCache tokenCache) { _config = config; _options = options; _eventService = eventService; + _clientService = clientService; _logger = logger; _tokenCache = tokenCache; @@ -134,6 +139,7 @@ private bool BeValidJwt(ClientDetails clientDetails, string clientAssertion) try { var handler = new JwtSecurityTokenHandler(); + Task.Run(async () => await _clientService.EnsureKid(clientDetails.ClientId, clientAssertion, tokenValidationParameters)).Wait(); handler.ValidateToken(clientAssertion, tokenValidationParameters, out var token); jwtToken = token as JwtSecurityToken; } diff --git a/Source/CDR.DataHolder.IdentityServer/Validation/CustomJwtRequestValidator.cs b/Source/CDR.DataHolder.IdentityServer/Validation/CustomJwtRequestValidator.cs index 952a963..626db88 100644 --- a/Source/CDR.DataHolder.IdentityServer/Validation/CustomJwtRequestValidator.cs +++ b/Source/CDR.DataHolder.IdentityServer/Validation/CustomJwtRequestValidator.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CDR.DataHolder.API.Infrastructure.Extensions; using CDR.DataHolder.IdentityServer.Configuration; +using CDR.DataHolder.IdentityServer.Services; using IdentityModel; using IdentityServer4.Extensions; using IdentityServer4.Models; @@ -26,15 +27,20 @@ public class CustomJwtRequestValidator { private readonly IConfiguration _configuration; private readonly ILogger _logger; + private readonly IClientService _clientService; /// /// Initializes a new instance of the class. /// Instantiates an instance of private_key_jwt secret validator /// - public CustomJwtRequestValidator(IConfiguration configuration, ILogger logger) + public CustomJwtRequestValidator( + IConfiguration configuration, + ILogger logger, + IClientService clientService) { _configuration = configuration; _logger = logger; + _clientService = clientService; } /// @@ -136,7 +142,7 @@ protected virtual Task> GetKeysAsync(Client client) /// The keys /// The client /// - protected virtual Task ValidateJwtAsync(string jwtTokenString, IEnumerable keys, Client client) + protected virtual async Task ValidateJwtAsync(string jwtTokenString, IEnumerable keys, Client client) { var validAudiences = new List { @@ -195,9 +201,9 @@ protected virtual Task ValidateJwtAsync(string jwtTokenString, }; var handler = new JwtSecurityTokenHandler(); + await _clientService.EnsureKid(client.ClientId, jwtTokenString, tokenValidationParameters); handler.ValidateToken(jwtTokenString, tokenValidationParameters, out var token); - - return Task.FromResult((JwtSecurityToken)token); + return (JwtSecurityToken)token; } /// diff --git a/Source/CDR.DataHolder.IdentityServer/Validation/PushedAuthorizationRequestValidator.cs b/Source/CDR.DataHolder.IdentityServer/Validation/PushedAuthorizationRequestValidator.cs index 27710a1..c8649ec 100644 --- a/Source/CDR.DataHolder.IdentityServer/Validation/PushedAuthorizationRequestValidator.cs +++ b/Source/CDR.DataHolder.IdentityServer/Validation/PushedAuthorizationRequestValidator.cs @@ -10,6 +10,7 @@ using CDR.DataHolder.IdentityServer.Interfaces; using CDR.DataHolder.IdentityServer.Logging; using CDR.DataHolder.IdentityServer.Models; +using CDR.DataHolder.IdentityServer.Services; using IdentityModel; using IdentityServer4; using IdentityServer4.Configuration; @@ -41,6 +42,7 @@ public class PushedAuthorizationRequestValidator : IPushedAuthorizationRequestVa private readonly IPersistedGrantStore _persistedGrantStore; private readonly ILogger _logger; private readonly ITokenReplayCache _tokenCache; + private readonly IClientService _clientService; private readonly ResponseTypeEqualityComparer _responseTypeEqualityComparer = new ResponseTypeEqualityComparer(); @@ -49,6 +51,7 @@ public PushedAuthorizationRequestValidator( IConfiguration config, IdentityServerOptions options, IClientStore clients, + IClientService clientService, ICustomAuthorizeRequestValidator customValidator, IRedirectUriValidator uriValidator, IPersistedGrantStore persistedGrantStore, @@ -61,6 +64,7 @@ public PushedAuthorizationRequestValidator( _options = options; _persistedGrantStore = persistedGrantStore; _clients = clients; + _clientService = clientService; _customValidator = customValidator; _uriValidator = uriValidator; _customJwtRequestValidator = customJwtRequestValidator; @@ -777,6 +781,7 @@ private bool BeValidClientAssertion(string clientAssertion, Microsoft.IdentityMo try { var handler = new JwtSecurityTokenHandler(); + Task.Run(async () => await _clientService.EnsureKid(clientId, clientAssertion, tokenValidationParameters)).Wait(); handler.ValidateToken(clientAssertion, tokenValidationParameters, out var token); jwtToken = token as JwtSecurityToken; } diff --git a/Source/CDR.DataHolder.IdentityServer/Views/Account/Login.cshtml b/Source/CDR.DataHolder.IdentityServer/Views/Account/Login.cshtml index d2dae26..0f1d664 100644 --- a/Source/CDR.DataHolder.IdentityServer/Views/Account/Login.cshtml +++ b/Source/CDR.DataHolder.IdentityServer/Views/Account/Login.cshtml @@ -24,6 +24,7 @@
@string.Format("Connect to {0}", data.dataHolderName)

Please provide your Customer ID to
connect @Model.ClientName to
@data.dataHolderName

+

Customer IDs available are: mmoss

diff --git a/Source/CDR.DataHolder.IntegrationTests/CDR.DataHolder.IntegrationTests.csproj b/Source/CDR.DataHolder.IntegrationTests/CDR.DataHolder.IntegrationTests.csproj index ec5a9af..8d152d4 100644 --- a/Source/CDR.DataHolder.IntegrationTests/CDR.DataHolder.IntegrationTests.csproj +++ b/Source/CDR.DataHolder.IntegrationTests/CDR.DataHolder.IntegrationTests.csproj @@ -2,9 +2,9 @@ net6.0 false - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.IntegrationTests/US28722_MDH_EnergyAPI_GetConcessions.cs b/Source/CDR.DataHolder.IntegrationTests/US28722_MDH_EnergyAPI_GetConcessions.cs index dbaf2f0..6d77990 100644 --- a/Source/CDR.DataHolder.IntegrationTests/US28722_MDH_EnergyAPI_GetConcessions.cs +++ b/Source/CDR.DataHolder.IntegrationTests/US28722_MDH_EnergyAPI_GetConcessions.cs @@ -47,15 +47,16 @@ private static (string, int) GetExpectedResponse( .Where(accountConcession => accountConcession.Account.AccountId == accountId) .Select(accountConcession => new { + type = accountConcession.Type, displayName = accountConcession.DisplayName, additionalInfo = accountConcession.AdditionalInfo, additionalInfoUri = accountConcession.AdditionalInfoUri, startDate = (accountConcession.StartDate ?? DateTime.MinValue).ToString("yyyy-MM-dd") ?? "", endDate = (accountConcession.EndDate ?? DateTime.MinValue).ToString("yyyy-MM-dd") ?? "", - dailyDiscount = accountConcession.DailyDiscount.ToString(), - monthlyDiscount = accountConcession.MonthlyDiscount.ToString(), - yearlyDiscount = accountConcession.YearlyDiscount.ToString(), - percentageDiscount = accountConcession.PercentageDiscount.ToString(), + discountFrequency = accountConcession.DiscountFrequency, + amount = accountConcession.Amount, + percentage = accountConcession.Percentage, + appliedTo = (accountConcession.AppliedTo ?? "").Split(',', StringSplitOptions.RemoveEmptyEntries), }) .ToList(); diff --git a/Source/CDR.DataHolder.Manage.API/CDR.DataHolder.Manage.API.csproj b/Source/CDR.DataHolder.Manage.API/CDR.DataHolder.Manage.API.csproj index 5d3e65f..de45d03 100644 --- a/Source/CDR.DataHolder.Manage.API/CDR.DataHolder.Manage.API.csproj +++ b/Source/CDR.DataHolder.Manage.API/CDR.DataHolder.Manage.API.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Manage.API/Data/seed-data.json b/Source/CDR.DataHolder.Manage.API/Data/seed-data.json index a66d54c..508ca5c 100644 --- a/Source/CDR.DataHolder.Manage.API/Data/seed-data.json +++ b/Source/CDR.DataHolder.Manage.API/Data/seed-data.json @@ -52,7 +52,9 @@ "DisplayName": "Pensioner", "StartDate": "2019-01-01", "EndDate": "2020-01-01", - "yearlyDiscount": "87.20" + "Type": "FIXED_AMOUNT", + "Amount": "87.20", + "DiscountFrequency": "P1Y" } ] }, @@ -279,14 +281,18 @@ "DisplayName": "Student", "StartDate": "2020-06-01", "EndDate": "2021-01-01", - "DailyDiscount": "0.13" + "Type": "FIXED_AMOUNT", + "Amount": "0.13", + "DiscountFrequency": "P1D" }, { "AccountConcessionId": "f40834f4-22eb-4e53-86f2-cd48bf1a35a6", "DisplayName": "Student Graduate", "StartDate": "2021-01-01", "EndDate": "2021-06-01", - "DailyDiscount": "0.11" + "Type": "FIXED_AMOUNT", + "Amount": "0.11", + "DiscountFrequency": "P1D" } ] } @@ -367,7 +373,9 @@ "DisplayName": "Loyalty Bonus", "StartDate": "2020-06-01", "EndDate": "2022-01-01", - "percentageDiscount": "7.5" + "Type": "FIXED_PERCENTAGE", + "Percentage": "7.5", + "DiscountFrequency": "P1M" } ] } @@ -448,14 +456,18 @@ "DisplayName": "Veteran", "StartDate": "2019-06-01", "EndDate": "2020-01-01", - "percentageDiscount": "23" + "Type": "FIXED_PERCENTAGE", + "Percentage": "23", + "DiscountFrequency": "P1Y" }, { "AccountConcessionId": "0696ae92-f851-487b-8e34-50bbf9387aa9", "DisplayName": "Pension", "StartDate": "2019-06-01", "EndDate": "2022-06-01", - "dailyDiscount": "0.29" + "Type": "FIXED_AMOUNT", + "Amount": "0.29", + "DiscountFrequency": "P1D" } ] } diff --git a/Source/CDR.DataHolder.Manage.API/Program.cs b/Source/CDR.DataHolder.Manage.API/Program.cs index 3395042..9e6a2b4 100644 --- a/Source/CDR.DataHolder.Manage.API/Program.cs +++ b/Source/CDR.DataHolder.Manage.API/Program.cs @@ -45,6 +45,14 @@ public static int Main(string[] args) } catch (Exception ex) { + // StopTheHostException - Unhandled exception from a BackgroundService thrown out of + // HostBuilder.Build() on adding a migration to a EF/.NET Core 6.0 RC2 project, needs to be dealt with gracefully. + string type = ex.GetType().Name; + if (type.Equals("StopTheHostException", StringComparison.Ordinal)) + { + throw; + } + Log.Fatal(ex, "Host terminated unexpectedly"); return 1; } diff --git a/Source/CDR.DataHolder.Manage.API/appsettings.Development.json b/Source/CDR.DataHolder.Manage.API/appsettings.Development.json index 18237fb..2862728 100644 --- a/Source/CDR.DataHolder.Manage.API/appsettings.Development.json +++ b/Source/CDR.DataHolder.Manage.API/appsettings.Development.json @@ -6,7 +6,7 @@ }, "SeedData": { "FilePath": "Data\\seed-data.json", - "OverwriteExistingData": false, + "OverwriteExistingData": true, "OffsetDates": false, "TimeSpan": 3 }, diff --git a/Source/CDR.DataHolder.Public.API/CDR.DataHolder.Public.API.csproj b/Source/CDR.DataHolder.Public.API/CDR.DataHolder.Public.API.csproj index 9f30f83..48db728 100644 --- a/Source/CDR.DataHolder.Public.API/CDR.DataHolder.Public.API.csproj +++ b/Source/CDR.DataHolder.Public.API/CDR.DataHolder.Public.API.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Repository/CDR.DataHolder.Repository.csproj b/Source/CDR.DataHolder.Repository/CDR.DataHolder.Repository.csproj index 43e8421..6163e6c 100644 --- a/Source/CDR.DataHolder.Repository/CDR.DataHolder.Repository.csproj +++ b/Source/CDR.DataHolder.Repository/CDR.DataHolder.Repository.csproj @@ -2,9 +2,9 @@ net6.0 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 @@ -27,9 +27,5 @@ - - - - diff --git a/Source/CDR.DataHolder.Repository/DataRecipientRepository.cs b/Source/CDR.DataHolder.Repository/DataRecipientRepository.cs new file mode 100644 index 0000000..062bc11 --- /dev/null +++ b/Source/CDR.DataHolder.Repository/DataRecipientRepository.cs @@ -0,0 +1,259 @@ +using CDR.DataHolder.Repository.Entities; +using CDR.DataHolder.Repository.Infrastructure; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CDR.DataHolder.Repository +{ + public class DataRecipientRepository + { + private readonly DbContextOptions _options; + + public DataRecipientRepository(string connString) + { + _options = new DbContextOptionsBuilder().UseSqlServer(connString).Options; + } + + public async Task<(IList, Exception)> GetDHDataRecipients() + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + var legalEntities = await dhDbContext.LegalEntities.AsNoTracking() + .Include(p => p.Brands) + .ThenInclude(brand => brand.SoftwareProducts) + .OrderBy(p => p.LegalEntityId) + .ToListAsync(); + + return (legalEntities, null); + } + } + catch (Exception ex) + { + return (null, ex); + } + } + + public async Task InsertDataRecipient(LegalEntity regDataRecipient) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + // Insert LegalEntity entity including its child Brands and SoftwareProducts entities + dhDbContext.Add(regDataRecipient); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task UpdateDataRecipient(LegalEntity regDr) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + var dhDrLegalEnt = await dhDbContext.LegalEntities + .Include(le => le.Brands) + .ThenInclude(b => b.SoftwareProducts) + .FirstOrDefaultAsync(le => le.LegalEntityId == regDr.LegalEntityId); + + if (dhDrLegalEnt != null) + { + dhDrLegalEnt.LegalEntityName = regDr.LegalEntityName; + dhDrLegalEnt.LogoUri = regDr.LogoUri; + dhDrLegalEnt.Status = regDr.Status; + dhDbContext.Update(dhDrLegalEnt); + + foreach (var regDrBrand in regDr.Brands) + { + var dhDrBrand = dhDrLegalEnt.Brands.FirstOrDefault(x => x.BrandId == regDrBrand.BrandId); + if (dhDrBrand != null) + { + dhDrBrand.BrandName = regDrBrand.BrandName; + dhDrBrand.LogoUri = regDrBrand.LogoUri; + dhDrBrand.Status = regDrBrand.Status; + dhDbContext.Update(dhDrBrand); + + foreach (var regDrSwProd in regDrBrand.SoftwareProducts) + { + var dhDrSwProd = dhDrBrand.SoftwareProducts.FirstOrDefault(x => x.SoftwareProductId == regDrSwProd.SoftwareProductId); + if (dhDrSwProd != null) + { + dhDrSwProd.SoftwareProductName = regDrSwProd.SoftwareProductName; + dhDrSwProd.SoftwareProductDescription = regDrSwProd.SoftwareProductDescription; + dhDrSwProd.LogoUri = regDrSwProd.LogoUri; + dhDrSwProd.Status = regDrSwProd.Status; + dhDbContext.Update(dhDrSwProd); + } + } + } + } + } + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task DeleteDataRecipients(IList dhDataRecipients) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + dhDbContext.RemoveRange(dhDataRecipients); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task InsertBrand(Brand regDrBrand) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + // Insert the Register Brand and any SoftwareProduct entities + IList swProducts = new List(); + foreach (var swProdItem in regDrBrand.SoftwareProducts) + { + swProducts.Add(new() + { + SoftwareProductId = swProdItem.SoftwareProductId, + SoftwareProductName = swProdItem.SoftwareProductName, + SoftwareProductDescription = swProdItem.SoftwareProductDescription, + LogoUri = swProdItem.LogoUri, + Status = swProdItem.Status + }); + } + Brand brand = new() + { + BrandId = regDrBrand.BrandId, + BrandName = regDrBrand.BrandName, + LogoUri = regDrBrand.LogoUri, + Status = regDrBrand.Status, + SoftwareProducts = swProducts, + LegalEntityId = regDrBrand.LegalEntityId + }; + + dhDbContext.Add(brand); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task DeleteBrands(IList dhBrands) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + dhDbContext.RemoveRange(dhBrands); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task InsertSoftwareProduct(SoftwareProduct regDrSwProd) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + // Insert the Register SoftwareProduct entity + SoftwareProduct swProduct = new() + { + SoftwareProductId = regDrSwProd.SoftwareProductId, + SoftwareProductName = regDrSwProd.SoftwareProductName, + SoftwareProductDescription = regDrSwProd.SoftwareProductDescription, + LogoUri = regDrSwProd.LogoUri, + Status = regDrSwProd.Status, + BrandId = regDrSwProd.BrandId + }; + + dhDbContext.Add(swProduct); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task DeleteSoftwareProduct(IList dhSwProds) + { + try + { + using (var dhDbContext = new DataHolderDatabaseContext(_options)) + { + using (var txn = dhDbContext.Database.BeginTransaction()) + { + dhDbContext.RemoveRange(dhSwProds); + await dhDbContext.SaveChangesAsync(); + await txn.CommitAsync(); + } + } + return null; + } + catch (Exception ex) + { + return ex; + } + } + } +} \ No newline at end of file diff --git a/Source/CDR.DataHolder.Repository/Entities/AccountConcession.cs b/Source/CDR.DataHolder.Repository/Entities/AccountConcession.cs index e626a96..c527e9d 100644 --- a/Source/CDR.DataHolder.Repository/Entities/AccountConcession.cs +++ b/Source/CDR.DataHolder.Repository/Entities/AccountConcession.cs @@ -1,6 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; -using CDR.DataHolder.Repository.Entities; +using System.ComponentModel.DataAnnotations.Schema; namespace CDR.DataHolder.Repository.Entities { @@ -13,6 +13,8 @@ public class AccountConcession public string AccountId { get; set; } public virtual Account Account { get; set; } + [MaxLength(1000), Required] + public string Type { get; set; } [Required, MaxLength(100)] public string DisplayName { get; set; } [MaxLength(1000)] @@ -21,9 +23,13 @@ public class AccountConcession public string AdditionalInfoUri { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } - public decimal? DailyDiscount { get; set; } - public decimal? MonthlyDiscount { get; set; } - public decimal? YearlyDiscount { get; set; } - public decimal? PercentageDiscount { get; set; } + [MaxLength(1000)] + public string DiscountFrequency { get; set; } + [MaxLength(1000)] + public string Amount { get; set; } + [MaxLength(1000)] + public string Percentage { get; set; } + [MaxLength(1000)] + public string AppliedTo { get; set; } } } \ No newline at end of file diff --git a/Source/CDR.DataHolder.Repository/Entities/LogEventsDrService.cs b/Source/CDR.DataHolder.Repository/Entities/LogEventsDrService.cs new file mode 100644 index 0000000..1b5601c --- /dev/null +++ b/Source/CDR.DataHolder.Repository/Entities/LogEventsDrService.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace CDR.DataHolder.Repository.Entities +{ + public class LogEventsDrService + { + [Key] + public int Id { get; set; } + + public string Message { get; set; } + + public string Level { get; set; } + + public DateTime TimeStamp { get; set; } + + public string Exception { get; set; } + + [MaxLength(50)] + public string Environment { get; set; } + + [MaxLength(50)] + public string ProcessId { get; set; } + + [MaxLength(50)] + public string ProcessName { get; set; } + + [MaxLength(50)] + public string ThreadId { get; set; } + + [MaxLength(50)] + public string MethodName { get; set; } + + [MaxLength(100)] + public string SourceContext { get; set; } + } +} \ No newline at end of file diff --git a/Source/CDR.DataHolder.Repository/Infrastructure/DataHolderDatabaseContext.cs b/Source/CDR.DataHolder.Repository/Infrastructure/DataHolderDatabaseContext.cs index 09feb28..dd969aa 100644 --- a/Source/CDR.DataHolder.Repository/Infrastructure/DataHolderDatabaseContext.cs +++ b/Source/CDR.DataHolder.Repository/Infrastructure/DataHolderDatabaseContext.cs @@ -24,6 +24,7 @@ public DataHolderDatabaseContext(DbContextOptions opt public DbSet Brands { get; set; } public DbSet SoftwareProducts { get; set; } + public DbSet LogEventsDrService { get; set; } public DbSet LogEventsManageAPI { get; set; } // Energy schema diff --git a/Source/CDR.DataHolder.Repository/Infrastructure/MappingProfile.cs b/Source/CDR.DataHolder.Repository/Infrastructure/MappingProfile.cs index 4b23366..af0155b 100644 --- a/Source/CDR.DataHolder.Repository/Infrastructure/MappingProfile.cs +++ b/Source/CDR.DataHolder.Repository/Infrastructure/MappingProfile.cs @@ -1,4 +1,5 @@ -using AutoMapper; +using System; +using AutoMapper; using CDR.DataHolder.Repository.Entities; using DomainEntities = CDR.DataHolder.Domain.Entities; namespace CDR.DataHolder.Repository.Infrastructure @@ -12,10 +13,9 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.StartDate, source => source.MapFrom(source => source.StartDate.HasValue ? source.StartDate.Value.ToString("yyyy-MM-dd") : null)) .ForMember(dest => dest.EndDate, source => source.MapFrom(source => source.EndDate.HasValue ? source.EndDate.Value.ToString("yyyy-MM-dd") : null)) - .ForMember(dest => dest.DailyDiscount, source => source.MapFrom(source => source.DailyDiscount.HasValue ? source.DailyDiscount.Value.ToString("F2") : null)) - .ForMember(dest => dest.MonthlyDiscount, source => source.MapFrom(source => source.MonthlyDiscount.HasValue ? source.MonthlyDiscount.Value.ToString("F2") : null)) - .ForMember(dest => dest.YearlyDiscount, source => source.MapFrom(source => source.YearlyDiscount.HasValue ? source.YearlyDiscount.Value.ToString("F2") : null)) - .ForMember(dest => dest.PercentageDiscount, source => source.MapFrom(source => source.PercentageDiscount.HasValue ? source.PercentageDiscount.Value.ToString(): null)) + .ForMember(dest => dest.Amount, source => source.MapFrom(source => string.IsNullOrEmpty(source.Amount) ? null : decimal.Parse(source.Amount).ToString("F2"))) + .ForMember(dest => dest.Percentage, source => source.MapFrom(source => string.IsNullOrEmpty(source.Percentage) ? null : decimal.Parse(source.Percentage).ToString("F2"))) + .ForMember(dest => dest.AppliedTo, source => source.MapFrom(source => string.IsNullOrEmpty(source.AppliedTo) ? Array.Empty() : source.AppliedTo.Split(',', StringSplitOptions.RemoveEmptyEntries))) .ReverseMap(); CreateMap() .ReverseMap(); diff --git a/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.Designer.cs b/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.Designer.cs new file mode 100644 index 0000000..dbf2022 --- /dev/null +++ b/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.Designer.cs @@ -0,0 +1,812 @@ +// +using System; +using CDR.DataHolder.Repository.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CDR.DataHolder.Repository.Migrations +{ + [DbContext(typeof(DataHolderDatabaseContext))] + [Migration("20220504053142_LogEventsDrService")] + partial class LogEventsDrService + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.Property("AccountId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("AccountId"); + + b.HasIndex("CustomerId"); + + b.ToTable("Account", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountConcession", b => + { + b.Property("AccountConcessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("AdditionalInfo") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AdditionalInfoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DailyDiscount") + .HasColumnType("decimal(18,2)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("MonthlyDiscount") + .HasColumnType("decimal(18,2)"); + + b.Property("PercentageDiscount") + .HasColumnType("decimal(18,2)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("YearlyDiscount") + .HasColumnType("decimal(18,2)"); + + b.HasKey("AccountConcessionId"); + + b.HasIndex("AccountId"); + + b.ToTable("AccountConcession", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.Property("AccountPlanId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("Nickname") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PlanId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.HasKey("AccountPlanId"); + + b.HasIndex("AccountId"); + + b.HasIndex("PlanId"); + + b.ToTable("AccountPlan", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LegalEntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("BrandId"); + + b.HasIndex("LegalEntityId"); + + b.ToTable("Brand", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerUType") + .HasColumnType("nvarchar(max)"); + + b.Property("LoginId") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("OrganisationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PersonId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("CustomerId"); + + b.HasIndex("OrganisationId") + .IsUnique() + .HasFilter("[OrganisationId] IS NOT NULL"); + + b.HasIndex("PersonId") + .IsUnique() + .HasFilter("[PersonId] IS NOT NULL"); + + b.ToTable("Customer", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LegalEntity", b => + { + b.Property("LegalEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LegalEntityName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("LegalEntityId"); + + b.ToTable("LegalEntity", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsDrService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Environment") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SourceContext") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ThreadId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TimeStamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("LogEventsDrService", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsManageAPI", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Environment") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SourceContext") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ThreadId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TimeStamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("LogEventsManageAPI", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Organisation", b => + { + b.Property("OrganisationId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Abn") + .HasMaxLength(11) + .HasColumnType("nvarchar(11)"); + + b.Property("Acn") + .HasMaxLength(9) + .HasColumnType("nvarchar(9)"); + + b.Property("AgentFirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AgentLastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AgentRole") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BusinessName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EstablishmentDate") + .HasColumnType("datetime2"); + + b.Property("IndustryCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("IndustryCodeVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAcnCRegistered") + .HasColumnType("bit"); + + b.Property("LastUpdateTime") + .HasColumnType("datetime2"); + + b.Property("LegalName") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrganisationType") + .HasColumnType("nvarchar(max)"); + + b.Property("RegisteredCountry") + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("ShortName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("OrganisationId"); + + b.ToTable("Organisation", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Person", b => + { + b.Property("PersonId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdateTime") + .HasColumnType("datetime2"); + + b.Property("MiddleNames") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OccupationCode") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("OccupationCodeVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("Prefix") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Suffix") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("PersonId"); + + b.ToTable("Person", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Plan", b => + { + b.Property("PlanId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ApplicationUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CustomerType") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime2"); + + b.Property("EffectiveTo") + .HasColumnType("datetime2"); + + b.Property("FuelType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("PlanId"); + + b.ToTable("Plan", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.PlanOverview", b => + { + b.Property("PlanOverviewId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountPlanId") + .HasColumnType("nvarchar(100)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("PlanOverviewId"); + + b.HasIndex("AccountPlanId") + .IsUnique() + .HasFilter("[AccountPlanId] IS NOT NULL"); + + b.ToTable("PlanOverview", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.ServicePoint", b => + { + b.Property("ServicePointId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountPlanId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("IsGenerator") + .HasColumnType("bit"); + + b.Property("JurisdictionCode") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdateDateTime") + .HasColumnType("datetime2"); + + b.Property("NationalMeteringId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ServicePointClassification") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ServicePointStatus") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ValidFromDate") + .HasColumnType("datetime2"); + + b.HasKey("ServicePointId"); + + b.HasIndex("AccountPlanId"); + + b.ToTable("ServicePoint", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.SoftwareProduct", b => + { + b.Property("SoftwareProductId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BrandId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SoftwareProductDescription") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SoftwareProductName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("SoftwareProductId"); + + b.HasIndex("BrandId"); + + b.ToTable("SoftwareProduct", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Transaction", b => + { + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("AccountId") + .HasColumnType("nvarchar(100)"); + + b.Property("Amount") + .HasPrecision(16, 2) + .HasColumnType("decimal(16,2)"); + + b.Property("ApcaNumber") + .HasMaxLength(6) + .HasColumnType("nvarchar(6)"); + + b.Property("BillerCode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("BillerName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Crn") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Currency") + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ExecutionDateTime") + .HasColumnType("datetime2"); + + b.Property("MerchantCategoryCode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("MerchantName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PostingDateTime") + .HasColumnType("datetime2"); + + b.Property("Reference") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionType") + .HasColumnType("nvarchar(max)"); + + b.Property("ValueDateTime") + .HasColumnType("datetime2"); + + b.HasKey("TransactionId"); + + b.HasIndex("AccountId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Customer", "Customer") + .WithMany("Accounts") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountConcession", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany("AccountConcessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany("AccountPlans") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CDR.DataHolder.Repository.Entities.Plan", "Plan") + .WithMany() + .HasForeignKey("PlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Plan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.LegalEntity", "LegalEntity") + .WithMany("Brands") + .HasForeignKey("LegalEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LegalEntity"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Organisation", "Organisation") + .WithOne("Customer") + .HasForeignKey("CDR.DataHolder.Repository.Entities.Customer", "OrganisationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CDR.DataHolder.Repository.Entities.Person", "Person") + .WithOne("Customer") + .HasForeignKey("CDR.DataHolder.Repository.Entities.Customer", "PersonId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organisation"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.PlanOverview", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.AccountPlan", "AccountPlan") + .WithOne("PlanOverview") + .HasForeignKey("CDR.DataHolder.Repository.Entities.PlanOverview", "AccountPlanId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AccountPlan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.ServicePoint", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.AccountPlan", "AccountPlan") + .WithMany("ServicePoints") + .HasForeignKey("AccountPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AccountPlan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.SoftwareProduct", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Brand", "Brand") + .WithMany("SoftwareProducts") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Transaction", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.Navigation("AccountConcessions"); + + b.Navigation("AccountPlans"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.Navigation("PlanOverview"); + + b.Navigation("ServicePoints"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.Navigation("SoftwareProducts"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LegalEntity", b => + { + b.Navigation("Brands"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Organisation", b => + { + b.Navigation("Customer"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Person", b => + { + b.Navigation("Customer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.cs b/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.cs new file mode 100644 index 0000000..41fe5ee --- /dev/null +++ b/Source/CDR.DataHolder.Repository/Migrations/20220504053142_LogEventsDrService.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CDR.DataHolder.Repository.Migrations +{ + public partial class LogEventsDrService : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LogEventsDrService", + columns: table => new + { + Id = table.Column(type: "int", nullable: false).Annotation("SqlServer:Identity", "1, 1"), + Message = table.Column(type: "nvarchar(max)", nullable: true), + Level = table.Column(type: "nvarchar(max)", nullable: true), + TimeStamp = table.Column(type: "datetime2", nullable: false), + Exception = table.Column(type: "nvarchar(max)", nullable: true), + Environment = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ProcessId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ProcessName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ThreadId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + MethodName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + SourceContext = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LogEventsDrService", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LogEventsDrService"); + } + } +} diff --git a/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.Designer.cs b/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.Designer.cs new file mode 100644 index 0000000..78707e0 --- /dev/null +++ b/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.Designer.cs @@ -0,0 +1,821 @@ +// +using System; +using CDR.DataHolder.Repository.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CDR.DataHolder.Repository.Migrations +{ + [DbContext(typeof(DataHolderDatabaseContext))] + [Migration("20220630000851_UpdateConcessionSchema")] + partial class UpdateConcessionSchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.Property("AccountId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("AccountId"); + + b.HasIndex("CustomerId"); + + b.ToTable("Account", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountConcession", b => + { + b.Property("AccountConcessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("AdditionalInfo") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AdditionalInfoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Amount") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AppliedTo") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscountFrequency") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("Percentage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.HasKey("AccountConcessionId"); + + b.HasIndex("AccountId"); + + b.ToTable("AccountConcession", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.Property("AccountPlanId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("Nickname") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PlanId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.HasKey("AccountPlanId"); + + b.HasIndex("AccountId"); + + b.HasIndex("PlanId"); + + b.ToTable("AccountPlan", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LegalEntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("BrandId"); + + b.HasIndex("LegalEntityId"); + + b.ToTable("Brand", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerUType") + .HasColumnType("nvarchar(max)"); + + b.Property("LoginId") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("OrganisationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PersonId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("CustomerId"); + + b.HasIndex("OrganisationId") + .IsUnique() + .HasFilter("[OrganisationId] IS NOT NULL"); + + b.HasIndex("PersonId") + .IsUnique() + .HasFilter("[PersonId] IS NOT NULL"); + + b.ToTable("Customer", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LegalEntity", b => + { + b.Property("LegalEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LegalEntityName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("LegalEntityId"); + + b.ToTable("LegalEntity", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsDrService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Environment") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SourceContext") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ThreadId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TimeStamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("LogEventsDrService", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsManageAPI", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Environment") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SourceContext") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ThreadId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TimeStamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("LogEventsManageAPI", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Organisation", b => + { + b.Property("OrganisationId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Abn") + .HasMaxLength(11) + .HasColumnType("nvarchar(11)"); + + b.Property("Acn") + .HasMaxLength(9) + .HasColumnType("nvarchar(9)"); + + b.Property("AgentFirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AgentLastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AgentRole") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BusinessName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EstablishmentDate") + .HasColumnType("datetime2"); + + b.Property("IndustryCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("IndustryCodeVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAcnCRegistered") + .HasColumnType("bit"); + + b.Property("LastUpdateTime") + .HasColumnType("datetime2"); + + b.Property("LegalName") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrganisationType") + .HasColumnType("nvarchar(max)"); + + b.Property("RegisteredCountry") + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("ShortName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("OrganisationId"); + + b.ToTable("Organisation", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Person", b => + { + b.Property("PersonId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdateTime") + .HasColumnType("datetime2"); + + b.Property("MiddleNames") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OccupationCode") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("OccupationCodeVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("Prefix") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Suffix") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("PersonId"); + + b.ToTable("Person", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Plan", b => + { + b.Property("PlanId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ApplicationUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CustomerType") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DisplayName") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime2"); + + b.Property("EffectiveTo") + .HasColumnType("datetime2"); + + b.Property("FuelType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("PlanId"); + + b.ToTable("Plan", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.PlanOverview", b => + { + b.Property("PlanOverviewId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountPlanId") + .HasColumnType("nvarchar(100)"); + + b.Property("DisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("PlanOverviewId"); + + b.HasIndex("AccountPlanId") + .IsUnique() + .HasFilter("[AccountPlanId] IS NOT NULL"); + + b.ToTable("PlanOverview", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.ServicePoint", b => + { + b.Property("ServicePointId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("AccountPlanId") + .IsRequired() + .HasColumnType("nvarchar(100)"); + + b.Property("IsGenerator") + .HasColumnType("bit"); + + b.Property("JurisdictionCode") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastUpdateDateTime") + .HasColumnType("datetime2"); + + b.Property("NationalMeteringId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ServicePointClassification") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ServicePointStatus") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ValidFromDate") + .HasColumnType("datetime2"); + + b.HasKey("ServicePointId"); + + b.HasIndex("AccountPlanId"); + + b.ToTable("ServicePoint", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.SoftwareProduct", b => + { + b.Property("SoftwareProductId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BrandId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SoftwareProductDescription") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SoftwareProductName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("SoftwareProductId"); + + b.HasIndex("BrandId"); + + b.ToTable("SoftwareProduct", (string)null); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Transaction", b => + { + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("AccountId") + .HasColumnType("nvarchar(100)"); + + b.Property("Amount") + .HasPrecision(16, 2) + .HasColumnType("decimal(16,2)"); + + b.Property("ApcaNumber") + .HasMaxLength(6) + .HasColumnType("nvarchar(6)"); + + b.Property("BillerCode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("BillerName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Crn") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Currency") + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ExecutionDateTime") + .HasColumnType("datetime2"); + + b.Property("MerchantCategoryCode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("MerchantName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PostingDateTime") + .HasColumnType("datetime2"); + + b.Property("Reference") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionType") + .HasColumnType("nvarchar(max)"); + + b.Property("ValueDateTime") + .HasColumnType("datetime2"); + + b.HasKey("TransactionId"); + + b.HasIndex("AccountId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Customer", "Customer") + .WithMany("Accounts") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountConcession", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany("AccountConcessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany("AccountPlans") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CDR.DataHolder.Repository.Entities.Plan", "Plan") + .WithMany() + .HasForeignKey("PlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Plan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.LegalEntity", "LegalEntity") + .WithMany("Brands") + .HasForeignKey("LegalEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LegalEntity"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Organisation", "Organisation") + .WithOne("Customer") + .HasForeignKey("CDR.DataHolder.Repository.Entities.Customer", "OrganisationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CDR.DataHolder.Repository.Entities.Person", "Person") + .WithOne("Customer") + .HasForeignKey("CDR.DataHolder.Repository.Entities.Customer", "PersonId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organisation"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.PlanOverview", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.AccountPlan", "AccountPlan") + .WithOne("PlanOverview") + .HasForeignKey("CDR.DataHolder.Repository.Entities.PlanOverview", "AccountPlanId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("AccountPlan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.ServicePoint", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.AccountPlan", "AccountPlan") + .WithMany("ServicePoints") + .HasForeignKey("AccountPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AccountPlan"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.SoftwareProduct", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Brand", "Brand") + .WithMany("SoftwareProducts") + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Brand"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Transaction", b => + { + b.HasOne("CDR.DataHolder.Repository.Entities.Account", "Account") + .WithMany() + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Account", b => + { + b.Navigation("AccountConcessions"); + + b.Navigation("AccountPlans"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.AccountPlan", b => + { + b.Navigation("PlanOverview"); + + b.Navigation("ServicePoints"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Brand", b => + { + b.Navigation("SoftwareProducts"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Customer", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LegalEntity", b => + { + b.Navigation("Brands"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Organisation", b => + { + b.Navigation("Customer"); + }); + + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.Person", b => + { + b.Navigation("Customer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.cs b/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.cs new file mode 100644 index 0000000..dc560c4 --- /dev/null +++ b/Source/CDR.DataHolder.Repository/Migrations/20220630000851_UpdateConcessionSchema.cs @@ -0,0 +1,111 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CDR.DataHolder.Repository.Migrations +{ + public partial class UpdateConcessionSchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DailyDiscount", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "MonthlyDiscount", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "PercentageDiscount", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "YearlyDiscount", + table: "AccountConcession"); + + migrationBuilder.AddColumn( + name: "Amount", + table: "AccountConcession", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true); + + migrationBuilder.AddColumn( + name: "AppliedTo", + table: "AccountConcession", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true); + + migrationBuilder.AddColumn( + name: "DiscountFrequency", + table: "AccountConcession", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true); + + migrationBuilder.AddColumn( + name: "Percentage", + table: "AccountConcession", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true); + + migrationBuilder.AddColumn( + name: "Type", + table: "AccountConcession", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: false, + defaultValue: ""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Amount", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "AppliedTo", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "DiscountFrequency", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "Percentage", + table: "AccountConcession"); + + migrationBuilder.DropColumn( + name: "Type", + table: "AccountConcession"); + + migrationBuilder.AddColumn( + name: "DailyDiscount", + table: "AccountConcession", + type: "decimal(18,2)", + nullable: true); + + migrationBuilder.AddColumn( + name: "MonthlyDiscount", + table: "AccountConcession", + type: "decimal(18,2)", + nullable: true); + + migrationBuilder.AddColumn( + name: "PercentageDiscount", + table: "AccountConcession", + type: "decimal(18,2)", + nullable: true); + + migrationBuilder.AddColumn( + name: "YearlyDiscount", + table: "AccountConcession", + type: "decimal(18,2)", + nullable: true); + } + } +} diff --git a/Source/CDR.DataHolder.Repository/Migrations/DataHolderDatabaseContextModelSnapshot.cs b/Source/CDR.DataHolder.Repository/Migrations/DataHolderDatabaseContextModelSnapshot.cs index b51f711..aa9748f 100644 --- a/Source/CDR.DataHolder.Repository/Migrations/DataHolderDatabaseContextModelSnapshot.cs +++ b/Source/CDR.DataHolder.Repository/Migrations/DataHolderDatabaseContextModelSnapshot.cs @@ -67,8 +67,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(1000) .HasColumnType("nvarchar(1000)"); - b.Property("DailyDiscount") - .HasColumnType("decimal(18,2)"); + b.Property("Amount") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AppliedTo") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DiscountFrequency") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); b.Property("DisplayName") .IsRequired() @@ -78,17 +87,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EndDate") .HasColumnType("datetime2"); - b.Property("MonthlyDiscount") - .HasColumnType("decimal(18,2)"); - - b.Property("PercentageDiscount") - .HasColumnType("decimal(18,2)"); + b.Property("Percentage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); b.Property("StartDate") .HasColumnType("datetime2"); - b.Property("YearlyDiscount") - .HasColumnType("decimal(18,2)"); + b.Property("Type") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); b.HasKey("AccountConcessionId"); @@ -212,6 +221,55 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LegalEntity", (string)null); }); + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsDrService", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Environment") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ProcessName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SourceContext") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ThreadId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TimeStamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("LogEventsDrService", (string)null); + }); + modelBuilder.Entity("CDR.DataHolder.Repository.Entities.LogEventsManageAPI", b => { b.Property("Id") diff --git a/Source/CDR.DataHolder.Resource.API.UnitTests/CDR.DataHolder.Resource.API.UnitTests.csproj b/Source/CDR.DataHolder.Resource.API.UnitTests/CDR.DataHolder.Resource.API.UnitTests.csproj index b3e9c21..da4f5e8 100644 --- a/Source/CDR.DataHolder.Resource.API.UnitTests/CDR.DataHolder.Resource.API.UnitTests.csproj +++ b/Source/CDR.DataHolder.Resource.API.UnitTests/CDR.DataHolder.Resource.API.UnitTests.csproj @@ -5,11 +5,11 @@ false - 0.1.0 + 1.0.0 - 0.1.0 + 1.0.0 - 0.1.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Resource.API/Business/Extensions.cs b/Source/CDR.DataHolder.Resource.API/Business/Extensions.cs index 3b3507a..af4e37b 100644 --- a/Source/CDR.DataHolder.Resource.API/Business/Extensions.cs +++ b/Source/CDR.DataHolder.Resource.API/Business/Extensions.cs @@ -6,22 +6,38 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; namespace CDR.DataHolder.Resource.API.Business { public static class Extensions { - public static Links GetLinks(this ControllerBase controller, string routeName, int? currentPage = null, int totalPages = 0, int? pageSize = null) + public static string GetHostName(this string url) { - string forwardedHost = null; - if (controller.Request.Headers.TryGetValue("X-Forwarded-Host", out StringValues forwardedHosts)) + return url.Replace("https://", "").Replace("http://", "").Split('/')[0]; + } + + public static Links GetLinks(this ControllerBase controller, string routeName, IConfiguration configuration, int? currentPage = null, int totalPages = 0, int? pageSize = null) + { + var resourceBaseUri = configuration.GetValue("ResourceBaseUri"); + var currentUrl = controller.Request.GetDisplayUrl(); + var selfLink = new Uri(currentUrl); + + if (string.IsNullOrEmpty(resourceBaseUri)) + { + if (controller.Request.Headers.TryGetValue("X-Forwarded-Host", out StringValues forwardedHosts)) + { + selfLink = ReplaceUriHost(currentUrl, forwardedHosts.First()); + } + } + else { - forwardedHost = forwardedHosts.First(); + var resourceHostName = resourceBaseUri.GetHostName(); + var currentHostName = currentUrl.GetHostName(); + selfLink = new Uri(currentUrl.Replace(currentHostName, resourceHostName)); } - var selfLink = ReplaceUriHost(controller.Request.GetDisplayUrl(), forwardedHost); - // Construct the non-paginated links. if (currentPage == null || totalPages == 0) { @@ -87,7 +103,7 @@ private static Uri ReplaceUriHost(string url, string newHost = null) var uriBuilder = new UriBuilder(url); if (!string.IsNullOrEmpty(newHost)) { - var segments = newHost.Split(':'); + var segments = newHost.Replace("https://", "").Split(':'); uriBuilder.Host = segments[0]; if (segments.Length > 1) diff --git a/Source/CDR.DataHolder.Resource.API/Business/Models/EnergyConcession.cs b/Source/CDR.DataHolder.Resource.API/Business/Models/EnergyConcession.cs index b0f2aba..4bb7800 100644 --- a/Source/CDR.DataHolder.Resource.API/Business/Models/EnergyConcession.cs +++ b/Source/CDR.DataHolder.Resource.API/Business/Models/EnergyConcession.cs @@ -2,14 +2,15 @@ { public class EnergyConcession { + public string Type { get; set; } public string DisplayName { get; set; } public string AdditionalInfo { get; set; } public string AdditionalInfoUri { get; set; } public string StartDate { get; set; } public string EndDate { get; set; } - public string DailyDiscount { get; set; } - public string MonthlyDiscount { get; set; } - public string YearlyDiscount { get; set; } - public string PercentageDiscount { get; set; } + public string DiscountFrequency { get; set; } + public string Amount { get; set; } + public string Percentage { get; set; } + public string[] AppliedTo { get; set; } } } diff --git a/Source/CDR.DataHolder.Resource.API/CDR.DataHolder.Resource.API.csproj b/Source/CDR.DataHolder.Resource.API/CDR.DataHolder.Resource.API.csproj index e32c35c..ae12409 100644 --- a/Source/CDR.DataHolder.Resource.API/CDR.DataHolder.Resource.API.csproj +++ b/Source/CDR.DataHolder.Resource.API/CDR.DataHolder.Resource.API.csproj @@ -3,9 +3,9 @@ net6.0 win-x64;linux-x64 - 0.1.0 - 0.1.0 - 0.1.0 + 1.0.0 + 1.0.0 + 1.0.0 diff --git a/Source/CDR.DataHolder.Resource.API/Controllers/ResourceController.cs b/Source/CDR.DataHolder.Resource.API/Controllers/ResourceController.cs index 2be12f9..1669733 100644 --- a/Source/CDR.DataHolder.Resource.API/Controllers/ResourceController.cs +++ b/Source/CDR.DataHolder.Resource.API/Controllers/ResourceController.cs @@ -11,6 +11,7 @@ using CDR.DataHolder.Resource.API.Business.Responses; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog.Context; using System; @@ -28,18 +29,22 @@ public class ResourceController : ControllerBase { private readonly IResourceRepository _resourceRepository; private readonly IStatusRepository _statusRepository; + private readonly IConfiguration _config; private readonly IMapper _mapper; private readonly ILogger _logger; private readonly IIdPermanenceManager _idPermanenceManager; - public ResourceController(IResourceRepository resourceRepository, - IStatusRepository statusRepository, - IMapper mapper, - ILogger logger, - IIdPermanenceManager idPermanenceManager) + public ResourceController( + IResourceRepository resourceRepository, + IStatusRepository statusRepository, + IConfiguration config, + IMapper mapper, + ILogger logger, + IIdPermanenceManager idPermanenceManager) { _resourceRepository = resourceRepository; _statusRepository = statusRepository; + _config = config; _mapper = mapper; _logger = logger; _idPermanenceManager = idPermanenceManager; @@ -78,7 +83,7 @@ public async Task GetCustomer() return BadRequest(); } - response.Links = this.GetLinks("GetCustomer"); + response.Links = this.GetLinks(nameof(GetCustomer), _config); return Ok(response); } @@ -139,7 +144,7 @@ public async Task GetEnergyAccounts( _idPermanenceManager.EncryptIds(response.Data.Accounts, idParameters, a => a.AccountId); // Set pagination meta data - response.Links = this.GetLinks(nameof(GetEnergyAccounts), pageNumber, response.Meta.TotalPages.GetValueOrDefault(), pageSizeNumber); + response.Links = this.GetLinks(nameof(GetEnergyAccounts), _config, pageNumber, response.Meta.TotalPages.GetValueOrDefault(), pageSizeNumber); return Ok(response); } @@ -227,7 +232,7 @@ public async Task GetConsessions([FromRoute] string accountId) var response = _mapper.Map(consessions); // Set pagination meta data - response.Links = this.GetLinks(nameof(GetConsessions)); + response.Links = this.GetLinks(nameof(GetConsessions), _config); return Ok(response); } diff --git a/Source/CDR.DataHolder.Resource.API/appsettings.Container.json b/Source/CDR.DataHolder.Resource.API/appsettings.Container.json index 64b15ed..24db373 100644 --- a/Source/CDR.DataHolder.Resource.API/appsettings.Container.json +++ b/Source/CDR.DataHolder.Resource.API/appsettings.Container.json @@ -81,5 +81,6 @@ }, "AccessTokenIntrospectionEndpoint": "https://host.docker.internal:8101/connect/introspect-internal", "IdentityServerIssuerUri": "https://host.docker.internal:8101", - "IdentityServerUrl": "https://host.docker.internal:8101" + "IdentityServerUrl": "https://host.docker.internal:8101", + "ResourceBaseUri": "https://host.docker.internal:8102" } \ No newline at end of file diff --git a/Source/CDR.DataHolder.Resource.API/appsettings.Development.json b/Source/CDR.DataHolder.Resource.API/appsettings.Development.json index 9df0bed..622b270 100644 --- a/Source/CDR.DataHolder.Resource.API/appsettings.Development.json +++ b/Source/CDR.DataHolder.Resource.API/appsettings.Development.json @@ -81,5 +81,6 @@ }, "AccessTokenIntrospectionEndpoint": "https://localhost:8101/connect/introspect-internal", "IdentityServerIssuerUri": "https://localhost:8101", - "IdentityServerUrl": "https://localhost:8101" + "IdentityServerUrl": "https://localhost:8101", + "ResourceBaseUri": "https://localhost:8102" } \ No newline at end of file diff --git a/Source/CDR.DataHolder.Resource.API/appsettings.Release.json b/Source/CDR.DataHolder.Resource.API/appsettings.Release.json index a76baba..f3d12cf 100644 --- a/Source/CDR.DataHolder.Resource.API/appsettings.Release.json +++ b/Source/CDR.DataHolder.Resource.API/appsettings.Release.json @@ -81,5 +81,6 @@ }, "AccessTokenIntrospectionEndpoint": "https://mock-data-holder-energy:8101/connect/introspect-internal", "IdentityServerIssuerUri": "https://mock-data-holder-energy:8101", - "IdentityServerUrl": "https://mock-data-holder-energy:8101" + "IdentityServerUrl": "https://mock-data-holder-energy:8101", + "ResourceBaseUri": "https://mock-data-holder-energy:8102" } diff --git a/Source/CDR.GetDataRecipients/.gitignore b/Source/CDR.GetDataRecipients/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/Source/CDR.GetDataRecipients/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/CDR.GetDataRecipients.csproj b/Source/CDR.GetDataRecipients/CDR.GetDataRecipients.csproj new file mode 100644 index 0000000..15cbfce --- /dev/null +++ b/Source/CDR.GetDataRecipients/CDR.GetDataRecipients.csproj @@ -0,0 +1,26 @@ + + + net6.0 + v4 + <_FunctionsSkipCleanOutput>true + 1.0.0 + 1.0.0 + 1.0.0 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/Source/CDR.GetDataRecipients/GetDataRecipients.cs b/Source/CDR.GetDataRecipients/GetDataRecipients.cs new file mode 100644 index 0000000..349f30f --- /dev/null +++ b/Source/CDR.GetDataRecipients/GetDataRecipients.cs @@ -0,0 +1,574 @@ +using CDR.DataHolder.Repository; +using CDR.DataHolder.Repository.Entities; +using Microsoft.Azure.WebJobs; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace CDR.GetDataRecipients +{ + public static class GetDataRecipientsFunction + { + public static string dbLoggingConnString; + public static string dbConnString; + + /// + /// Get Data Recipients Function + /// + /// Gets the Data Recipients from the Register and updates the local repository + [FunctionName("GetDataRecipients")] + public static async Task DATARECIPIENTS([TimerTrigger("%Schedule%")] TimerInfo myTimer, ILogger log, ExecutionContext context) + { + try + { + var isLocalDev = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT").Equals("Development"); + var configBuilder = new ConfigurationBuilder().SetBasePath(context.FunctionAppDirectory); + + if (isLocalDev) + { + configBuilder = configBuilder.AddJsonFile("local.settings.json", optional: false, reloadOnChange: true); + } + + var config = configBuilder.AddEnvironmentVariables().Build(); + + // Set connection strings. + dbLoggingConnString = Environment.GetEnvironmentVariable("DataHolder_Logging_DB_ConnectionString"); + dbConnString = Environment.GetEnvironmentVariable("DataHolder_DB_ConnectionString"); + + string dataRecipientsEndpoint = Environment.GetEnvironmentVariable("Register_GetDataRecipients_Endpoint"); + string xvVer = Environment.GetEnvironmentVariable("Register_GetDataRecipients_XV"); + bool ignoreServerCertificateErrors = Environment.GetEnvironmentVariable("Ignore_Server_Certificate_Errors").Equals("true", StringComparison.OrdinalIgnoreCase); + + (string dataRecipientJson, System.Net.HttpStatusCode respStatusCode) = await GetDataRecipients(dataRecipientsEndpoint, xvVer, log, ignoreServerCertificateErrors); + if (respStatusCode == System.Net.HttpStatusCode.OK) + { + // Get Register -> Data Recipients + JsonSerializerSettings jss = new() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + var data = JsonConvert.DeserializeObject(dataRecipientJson, jss); + var dataRecipients = data["data"].ToObject(); + IList regDataRecipients = new List(); + foreach (var item in dataRecipients) + { + regDataRecipients.Add(item); + } + + // Get Data Holder -> Data Recipients + (IList dhDataRecipients, Exception ex) = await new DataRecipientRepository(dbConnString).GetDHDataRecipients(); + + if (ex != null) + { + log.LogError(ex, "An exception occurred retrieving data recipients from the database."); + throw ex; + } + + // Compare Register and Data Holder -> Data Recipients + await CompareDataRecipients(regDataRecipients, dhDataRecipients, log); + } + } + catch (Exception ex) + { + await InsertDBLog(dbLoggingConnString, "Error", "Exception", "DATARECIPIENTS", ex); + } + } + + /// + /// Get the list of Data Recipients from the Register + /// + /// Raw data + private static async Task<(string, System.Net.HttpStatusCode)> GetDataRecipients( + string dataRecipientsEndpoint, + string version, + ILogger log, + bool ignoreServerCertificateErrors = false) + { + var client = GetHttpClient(version, ignoreServerCertificateErrors); + + log.LogInformation("Retrieving data recipients from the Register: {dataRecipientsEndpoint}", dataRecipientsEndpoint); + var response = await client.GetAsync(dataRecipientsEndpoint); + var data = await response.Content.ReadAsStringAsync(); + log.LogInformation("Register response: {statusCode} - {body}", response.StatusCode, data); + return (data, response.StatusCode); + } + + private static HttpClient GetHttpClient( + string version = null, + bool ignoreServerCertificateErrors = false) + { + var clientHandler = new HttpClientHandler(); + + if (ignoreServerCertificateErrors) + { + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + } + + var client = new HttpClient(clientHandler); + + // Add the x-v header to the request if provided. + if (!string.IsNullOrEmpty(version)) + { + client.DefaultRequestHeaders.Add("x-v", version); + } + + return client; + } + + private static async Task CompareDataRecipients(IList regDataRecipients, IList dhDataRecipients, ILogger log) + { + try + { + IList insDataRecipients = new List(); + IList delDataRecipients = new List(); + IList insBrands = new List(); + IList delBrands = new List(); + IList insSwProds = new List(); + IList delSwProds = new List(); + IList updDataRecipients = new List(); + + if (regDataRecipients.Any()) + { + // Compare each Register to Data Holder repo Data Recipient + foreach (var regDataRecipient in regDataRecipients) + { + await ProcessCompareDataRecipients(dhDataRecipients, regDataRecipient, insDataRecipients, insBrands, delBrands, insSwProds, delSwProds, updDataRecipients); + } + + // Are there ANY Data Recipients in the Data Holder that ARE NOT in the Register? + IList dhDrs = dhDataRecipients.Where(n => !regDataRecipients.Any(o => o.LegalEntityId.CompareTo(n.LegalEntityId) == 0)).ToList(); + if (dhDrs.Any()) + { + foreach (var dhDr in dhDrs) + { + delDataRecipients.Add(dhDr); + } + } + } + + // INSERT Register Data Recipients including its child Brands and Software Products into Data Holder repo + if (insDataRecipients.Any()) + { + foreach (var insDr in insDataRecipients) + { + await InsertDhDataRecipient(insDr); + } + insDataRecipients.Clear(); + } + + // DELETE Data Holder repo Data Recipients that ARE NOT in the Register + if (delDataRecipients.Any()) + { + await DeleteDhDataRecipients(delDataRecipients); + delDataRecipients.Clear(); + } + + // INSERT Register Brands into Data Holder repo + if (insBrands.Any()) + { + foreach (var insBrand in insBrands) + { + await InsertDhDrBrand(insBrand); + } + insBrands.Clear(); + } + + // DELETE Brands from Data Holder repo that ARE NOT in the Register + if (delBrands.Any()) + { + await DeleteDhBrands(delBrands); + delBrands.Clear(); + } + + // INSERT Register Software Products into Data Holder repo + if (insSwProds.Any()) + { + foreach (var insSwProd in insSwProds) + { + await InsertDhDrSwProd(insSwProd); + } + insSwProds.Clear(); + } + + // DELETE Software Products from Data Holder repo that ARE NOT in the Register + if (delSwProds.Any()) + { + await DeleteDhSwProducts(delSwProds); + delSwProds.Clear(); + } + + // UPDATE Data Holder repo with Register Data Recipients + if (updDataRecipients.Any()) + { + foreach (var updDr in updDataRecipients) + { + await UpdateDhDataRecipient(updDr); + } + updDataRecipients.Clear(); + } + } + catch (Exception ex) + { + await InsertDBLog(dbLoggingConnString, "Error", "Exception", "CompareDataRecipients", ex); + } + } + + private static async Task ProcessCompareDataRecipients(IList dhDataRecipients, LegalEntity regDataRecipient, IList insDataRecipients, IList insBrands, IList delBrands, IList insSwProds, IList delSwProds, IList updDataRecipients) + { + try + { + // DOES this Register Data Recipient --> + LegalEntity dhDataRecipient = null; + + // EXIST in the Data Holder repo? + dhDataRecipient = dhDataRecipients.FirstOrDefault(n => n.LegalEntityId.CompareTo(regDataRecipient.LegalEntityId) == 0); + if (dhDataRecipient == null) + { + // NO - AM I in the Insert List? + var alreadyInList = insDataRecipients.FirstOrDefault(x => x.LegalEntityId.CompareTo(regDataRecipient.LegalEntityId) == 0); + if (alreadyInList == null) + { + insDataRecipients.Add(regDataRecipient); + } + return; + } + + // Register Data Recipient -> Legal Entity ONLY - NO Brands or Software Products + if (!regDataRecipient.Brands.Any()) + { + // DO Data Holder Brands EXIST? (if yes then delete them) + if (dhDataRecipient.Brands.Any()) + { + foreach (var brand in dhDataRecipient.Brands) + { + delBrands.Add(brand); + } + } + return; + } + + foreach (var regDrBrand in regDataRecipient.Brands) + { + // Register Data Recipient -> Legal Entity and Brand ONLY - NO Software Products + if (!regDrBrand.SoftwareProducts.Any()) + { + // DO Data Holder Brands EXIST? + IList dhDrBrands = dhDataRecipient.Brands.Where(x => x.BrandId.CompareTo(regDrBrand.BrandId) == 0).ToList(); + if (dhDrBrands.Any()) + { + foreach (var brand in dhDrBrands) + { + // DO Data Holder Software Products EXIST? (if yes then delete them) + foreach (var swProd in brand.SoftwareProducts) + { + delSwProds.Add(swProd); + } + } + } + } + else + { + // Register Data Recipient -> Legal Entity, Brands and Software Products + foreach (var regDrSwProd in regDrBrand.SoftwareProducts) + { + await CompareRegToDh(dhDataRecipients, regDataRecipient, regDrBrand, regDrSwProd, insBrands, insSwProds, delSwProds, updDataRecipients); + } + } + } + } + catch (Exception ex) + { + await InsertDBLog(dbLoggingConnString, "Error", "Exception", "ProcessCompareDataRecipients", ex); + } + } + + private static async Task CompareRegToDh(IList dhDataRecipients, LegalEntity regDr, Brand regDrBrand, SoftwareProduct regDrSwProd, IList insBrands, IList insSwProds, IList delSwProds, IList updDrs) + { + try + { + // DOES this Register Data Recipient --> ; + LegalEntity dhDataRecipient = null; + + // DOES the Brand exist in the Data Holder repo? + dhDataRecipient = dhDataRecipients.FirstOrDefault(n => n.Brands.Any(p => p.BrandId.CompareTo(regDrBrand.BrandId) == 0)); + if (dhDataRecipient == null) + { + // NO - AM I in the Insert List? + // (plausible to try to INSERT for each of the Brands in the Data Recipient, this violates PRIMARY KEY constraint PK_Brand) + var alreadyInList = insBrands.FirstOrDefault(x => x.BrandId.CompareTo(regDrBrand.BrandId) == 0); + if (alreadyInList == null) + { + regDrBrand.LegalEntityId = regDr.LegalEntityId; + insBrands.Add(regDrBrand); + } + } + else + { + // DOES the Software Product exist in the Data Holder repo? + dhDataRecipient = dhDataRecipients.FirstOrDefault(n => n.Brands.Any(p => p.SoftwareProducts.Any(q => q.SoftwareProductId.CompareTo(regDrSwProd.SoftwareProductId) == 0))); + if (dhDataRecipient == null) + { + regDrSwProd.BrandId = regDrBrand.BrandId; + insSwProds.Add(regDrSwProd); + } + } + + if (dhDataRecipient != null) + { + // DOES the Data Holder repo differ to Register? + foreach (var dhDrBrand in dhDataRecipient.Brands) + { + if (dhDrBrand.SoftwareProducts.Count > regDrBrand.SoftwareProducts.Count) + { + foreach (var dhSwProd in dhDrBrand.SoftwareProducts) + { + // FIND this Data Holder Software Product in the Register Software Products + var regSwProd = regDrBrand.SoftwareProducts.FirstOrDefault(x => x.SoftwareProductId.CompareTo(dhSwProd.SoftwareProductId) == 0); + if (regSwProd == null) + { + // NOT FOUND, REMOVE -> this Data Holder Software Product - it DOES NOT EXIST in the Register + var alreadyInList = delSwProds.FirstOrDefault(x => x.SoftwareProductId.CompareTo(dhSwProd.SoftwareProductId) == 0); + if (alreadyInList == null) + delSwProds.Add(dhSwProd); + } + } + } + else if (dhDrBrand.SoftwareProducts.Count == regDrBrand.SoftwareProducts.Count) + { + foreach (var dhSwProd in dhDrBrand.SoftwareProducts) + { + LegalEntity upRegDr = await CompareEntityProperties(dhDataRecipient, dhDrBrand, dhSwProd, regDr, regDrBrand, regDrSwProd); + if (upRegDr != null) + updDrs.Add(upRegDr); + } + } + } + } + } + catch (Exception ex) + { + await InsertDBLog(dbLoggingConnString, "Error", "Exception", "CompareRegToDh", ex); + } + } + + private static async Task CompareEntityProperties(LegalEntity dhDr, Brand dhDrBrand, SoftwareProduct dhDrSWProd, LegalEntity regDr, Brand regDrBrand, SoftwareProduct regDrSWProd) + { + try + { + // UPDATE -> Compare Legal Entity properties + if (dhDr.LegalEntityId.CompareTo(regDr.LegalEntityId) == 0) + { + if (string.Compare(dhDr.LegalEntityName, regDr.LegalEntityName, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDr.LogoUri, regDr.LogoUri, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDr.Status, regDr.Status, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + } + + // UPDATE -> Compare Brand entity properties + if (dhDrBrand.BrandId.CompareTo(regDrBrand.BrandId) == 0) + { + if (string.Compare(dhDrBrand.BrandName, regDrBrand.BrandName, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDrBrand.LogoUri, regDrBrand.LogoUri, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDrBrand.Status, regDrBrand.Status, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + } + + // UPDATE -> Compare Software Product entity properties + if (dhDrSWProd.SoftwareProductId.CompareTo(regDrSWProd.SoftwareProductId) == 0) + { + if (string.Compare(dhDrSWProd.SoftwareProductName, regDrSWProd.SoftwareProductName, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDrSWProd.SoftwareProductDescription, regDrSWProd.SoftwareProductDescription, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDrSWProd.LogoUri, regDrSWProd.LogoUri, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + + else if (string.Compare(dhDrSWProd.Status, regDrSWProd.Status, StringComparison.OrdinalIgnoreCase) != 0) + return regDr; + } + } + catch (Exception ex) + { + await InsertDBLog(dbLoggingConnString, "Error", "Exception", "CompareEntityProperties", ex); + } + return null; + } + + private static async Task InsertDhDataRecipient(LegalEntity regDr) + { + Exception ex = await new DataRecipientRepository(dbConnString).InsertDataRecipient(regDr); + if (ex == null) + await InsertDBLog(dbLoggingConnString, $"Added - {regDr.LegalEntityName} - ({regDr.LegalEntityId})", "Information", "InsertRegDr"); + else + await InsertDBLog(dbLoggingConnString, "", "Exception", "InsertDhDataRecipient", ex, SerialiseEntity(regDr)); + } + + private static async Task InsertDhDrBrand(Brand regDrBrand) + { + Exception ex = await new DataRecipientRepository(dbConnString).InsertBrand(regDrBrand); + if (ex == null) + await InsertDBLog(dbLoggingConnString, $"Added - {regDrBrand.BrandName} - ({regDrBrand.BrandId})", "Information", "InsertRegDrBrand"); + else + await InsertDBLog(dbLoggingConnString, "", "Exception", "InsertDhDrBrand", ex, SerialiseEntity(regDrBrand)); + } + private static async Task InsertDhDrSwProd(SoftwareProduct regDrSwProd) + { + Exception ex = await new DataRecipientRepository(dbConnString).InsertSoftwareProduct(regDrSwProd); + if (ex == null) + await InsertDBLog(dbLoggingConnString, $"Added - {regDrSwProd.SoftwareProductName} - ({regDrSwProd.SoftwareProductId})", "Information", "InsertRegDrSwProd"); + else + await InsertDBLog(dbLoggingConnString, "", "Exception", "InsertDhDrSwProd", ex, SerialiseEntity(regDrSwProd)); + } + + private static async Task UpdateDhDataRecipient(LegalEntity regDr) + { + Exception ex = await new DataRecipientRepository(dbConnString).UpdateDataRecipient(regDr); + if (ex == null) + await InsertDBLog(dbLoggingConnString, $"Updated - {regDr.LegalEntityName} - ({regDr.LegalEntityId})", "Information", "UpdateDhDr"); + else + await InsertDBLog(dbLoggingConnString, "", "Exception", "UpdateDhDataRecipient", ex, SerialiseEntity(regDr)); + } + + private static async Task DeleteDhDataRecipients(IList dhDataRecipients) + { + Exception ex = await new DataRecipientRepository(dbConnString).DeleteDataRecipients(dhDataRecipients); + if (ex == null) + { + foreach (var dhDataRecipient in dhDataRecipients) + { + await InsertDBLog(dbLoggingConnString, $"Deleted - {dhDataRecipient.LegalEntityName} - ({dhDataRecipient.LegalEntityId})", "Information", "DeleteDhDataRecipients"); + } + } + else + { + await InsertDBLog(dbLoggingConnString, "", "Exception", "DeleteDhDataRecipients", ex, SerialiseEntity(dhDataRecipients)); + } + } + + private static async Task DeleteDhBrands(IList dhBrands) + { + Exception ex = await new DataRecipientRepository(dbConnString).DeleteBrands(dhBrands); + if (ex == null) + { + foreach (var dhBrand in dhBrands) + { + await InsertDBLog(dbLoggingConnString, $"Deleted - {dhBrand.BrandName} - ({dhBrand.BrandId})", "Information", "DeleteDhBrands"); + } + } + else + { + await InsertDBLog(dbLoggingConnString, "", "Exception", "DeleteDhBrands", ex, SerialiseEntity(dhBrands)); + } + } + + private static async Task DeleteDhSwProducts(IList dhSwProds) + { + Exception ex = await new DataRecipientRepository(dbConnString).DeleteSoftwareProduct(dhSwProds); + if (ex == null) + { + foreach (var dhSwProd in dhSwProds) + { + await InsertDBLog(dbLoggingConnString, $"Deleted - {dhSwProd.SoftwareProductName} - ({dhSwProd.SoftwareProductId})", "Information", "DeleteDhSwProds"); + } + } + else + { + await InsertDBLog(dbLoggingConnString, "", "Exception", "DeleteDhSwProducts", ex, SerialiseEntity(dhSwProds)); + } + } + + private static string SerialiseEntity(object ent) + { + JsonSerializerSettings jss = new() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + return JsonConvert.SerializeObject(ent, jss); + } + + /// + /// Update the Log table + /// + private static async Task InsertDBLog(string dbConnString, string msg, string lvl, string methodName, Exception exMsg = null, string entity = "") + { + string exMessage = ""; + + if (exMsg != null) + { + Exception innerException = exMsg; + StringBuilder innerMsg = new(); + int ctr = 0; + + do + { + // skip the first inner exeception message as it is the same as the exception message + if (ctr > 0) + { + innerMsg.Append(string.IsNullOrEmpty(innerException.Message) ? string.Empty : innerException.Message); + innerMsg.Append("\r\n"); + } + else + { + ctr++; + } + + innerException = innerException.InnerException; + } + while (innerException != null); + + // USE the EXCEPTION MESSAGE + if (innerMsg.Length == 0) + exMessage = exMsg.Message; + + // USE the INNER EXCEPTION MESSAGE (INCLUDES the EXCEPTION MESSAGE) + else + exMessage = innerMsg.ToString(); + + // Include the serialised entity for use with EXCEPTION message ONLY + if (!string.IsNullOrEmpty(entity)) + exMessage += "\r\nEntity: " + entity; + + exMessage = exMessage.Replace("'", ""); + } + + using (SqlConnection db = new(dbConnString)) + { + db.Open(); + var cmdText = ""; + + if (string.IsNullOrEmpty(exMessage)) + cmdText = $"INSERT INTO [LogEventsDrService] ([Message], [Level], [TimeStamp], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(),@procName,@methodName,@srcContext)"; + else + cmdText = $"INSERT INTO [LogEventsDrService] ([Message], [Level], [TimeStamp], [Exception], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(), @exMessage,@procName,@methodName,@srcContext)"; + + using var cmd = new SqlCommand(cmdText, db); + cmd.Parameters.AddWithValue("@msg", msg); + cmd.Parameters.AddWithValue("@lvl", lvl); + cmd.Parameters.AddWithValue("@exMessage", exMessage); + cmd.Parameters.AddWithValue("@procName", "Azure Function"); + cmd.Parameters.AddWithValue("@methodName", methodName); + cmd.Parameters.AddWithValue("@srcContext", "CDR.GetDataRecipients"); + await cmd.ExecuteNonQueryAsync(); + db.Close(); + } + } + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/Properties/launchSettings.json b/Source/CDR.GetDataRecipients/Properties/launchSettings.json new file mode 100644 index 0000000..72fdfc4 --- /dev/null +++ b/Source/CDR.GetDataRecipients/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "GetDataRecipients": { + "commandName": "Project", + "commandLineArgs": "host start --verbose" + } + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/Properties/serviceDependencies.json b/Source/CDR.GetDataRecipients/Properties/serviceDependencies.json new file mode 100644 index 0000000..df4dcc9 --- /dev/null +++ b/Source/CDR.GetDataRecipients/Properties/serviceDependencies.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/Properties/serviceDependencies.local.json b/Source/CDR.GetDataRecipients/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..155d87e --- /dev/null +++ b/Source/CDR.GetDataRecipients/Properties/serviceDependencies.local.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/azure.settings.json b/Source/CDR.GetDataRecipients/azure.settings.json new file mode 100644 index 0000000..7050a8a --- /dev/null +++ b/Source/CDR.GetDataRecipients/azure.settings.json @@ -0,0 +1,14 @@ +{ + "Values": { + "StorageConnectionString": "", + "Schedule": "" + }, + "ConnectionStrings": { + "DataHolder_DB": "", + "DataHolder_Logging_DB": "" + }, + "Register": { + "GetDataRecipientsEndpoint": "", + "xvVersion": "" + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/host.json b/Source/CDR.GetDataRecipients/host.json new file mode 100644 index 0000000..5ae5a72 --- /dev/null +++ b/Source/CDR.GetDataRecipients/host.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensions": { + "http": { + "routePrefix": "" + } + } +} \ No newline at end of file diff --git a/Source/CDR.GetDataRecipients/local.settings.json b/Source/CDR.GetDataRecipients/local.settings.json new file mode 100644 index 0000000..f79cd7a --- /dev/null +++ b/Source/CDR.GetDataRecipients/local.settings.json @@ -0,0 +1,19 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "StorageConnectionString": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "Schedule": "0-59 * * * *", + "DataHolder_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdhe;Integrated Security=true", + "DataHolder_Logging_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdhe;Integrated Security=true", + "Register_GetDataRecipients_Endpoint": "https://localhost:7000/cdr-register/v1/all/data-recipients", + "Register_GetDataRecipients_XV": "3", + "Ignore_Server_Certificate_Errors": "false" + }, + "Host": { + "LocalHttpPort": 7074, + "CORS": "*", + "CORSCredentials": false + } +} \ No newline at end of file diff --git a/Source/Dockerfile b/Source/Dockerfile index b709801..029b9de 100644 --- a/Source/Dockerfile +++ b/Source/Dockerfile @@ -1,6 +1,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app -EXPOSE 80 EXPOSE 8101 EXPOSE 8102 @@ -58,6 +57,17 @@ RUN sudo cp ./gateway-mtls/Certificates/ca.crt /usr/local/share/ca-certificates/ RUN sudo update-ca-certificates +RUN addgroup --group appgroup --gid 2000 \ +&& adduser \ + --uid 1000 \ + --gid 2000 \ + "appuser" +RUN chown -R appuser:appgroup /app +RUN chown -R appuser:appgroup /usr/bin +RUN chown -R appuser:appgroup /usr/local +RUN chown -R appuser:appgroup /tmp +USER appuser:appgroup + ENV ASPNETCORE_URLS=https://+:8101;https://+:8102 -ENTRYPOINT ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"] +ENTRYPOINT ["/usr/bin/supervisord", "-c", "/app/supervisord.conf", "-u", "1000"] diff --git a/Source/Dockerfile.for-testing b/Source/Dockerfile.for-testing new file mode 100644 index 0000000..b709801 --- /dev/null +++ b/Source/Dockerfile.for-testing @@ -0,0 +1,63 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 8101 +EXPOSE 8102 + +# Default ASPNETCORE_ENVIRONMENT to Release +ENV ASPNETCORE_ENVIRONMENT=Release + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src + +COPY . ./ + +FROM build AS publish + +COPY ./CDR.DataHolder.API.Infrastructure/. /app/CDR.DataHolder.API.Infrastructure +COPY ./CDR.DataHolder.Repository/. /app/CDR.DataHolder.Repository +COPY ./CDR.DataHolder.Domain/. /app/CDR.DataHolder.Domain +COPY ./CDR.DataHolder.Admin.API/. /app/CDR.DataHolder.Admin.API +COPY ./CDR.DataHolder.Manage.API/. /app/CDR.DataHolder.Manage.API +COPY ./CDR.DataHolder.Public.API/. /app/CDR.DataHolder.Public.API +COPY ./CDR.DataHolder.Resource.API/. /app/CDR.DataHolder.Resource.API +COPY ./CDR.DataHolder.IdentityServer/. /app/CDR.DataHolder.IdentityServer +COPY ./CDR.DataHolder.API.Gateway.mTLS/. /app/CDR.DataHolder.API.Gateway.mTLS + +WORKDIR /app/CDR.DataHolder.Admin.API +RUN dotnet publish -c Release -o /app/publish/admin +WORKDIR /app/CDR.DataHolder.Manage.API +RUN dotnet publish -c Release -o /app/publish/manage +WORKDIR /app/CDR.DataHolder.Public.API +RUN dotnet publish -c Release -o /app/publish/public +WORKDIR /app/CDR.DataHolder.Resource.API +RUN dotnet publish -c Release -o /app/publish/resource +WORKDIR /app/CDR.DataHolder.IdentityServer +RUN dotnet publish -c Release -o /app/publish/idsvr +WORKDIR /app/CDR.DataHolder.API.Gateway.mTLS +RUN dotnet publish -c Release -o /app/publish/gateway-mtls + +COPY supervisord.conf /app/publish/supervisord.conf + +FROM base AS final +WORKDIR /app + +COPY --from=publish /app/publish/supervisord.conf . +COPY --from=publish /app/publish/resource ./resource +COPY --from=publish /app/publish/admin ./admin +COPY --from=publish /app/publish/manage ./manage +COPY --from=publish /app/publish/idsvr ./idsvr +COPY --from=publish /app/publish/gateway-mtls ./gateway-mtls +COPY --from=publish /app/publish/public ./public + +RUN apt-get update && apt-get install -y supervisor + +RUN apt-get update && apt-get install -y sudo + +RUN sudo cp ./gateway-mtls/Certificates/ca.crt /usr/local/share/ca-certificates/ca.crt + +RUN sudo update-ca-certificates + +ENV ASPNETCORE_URLS=https://+:8101;https://+:8102 + +ENTRYPOINT ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"] diff --git a/Source/EnergyDataHolder.sln b/Source/EnergyDataHolder.sln index ad3a610..f1dbdde 100644 --- a/Source/EnergyDataHolder.sln +++ b/Source/EnergyDataHolder.sln @@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDR.DataHolder.IdentityServ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDR.DataHolder.Resource.API.UnitTests", "CDR.DataHolder.Resource.API.UnitTests\CDR.DataHolder.Resource.API.UnitTests.csproj", "{97F4558A-39C5-4FD4-957F-DD501806FCFB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDR.GetDataRecipients", "CDR.GetDataRecipients\CDR.GetDataRecipients.csproj", "{E353178F-9B75-4281-85A7-A66FF04277EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "070 Functions", "070 Functions", "{B98275D2-3CED-483A-AAC5-089F4CEC5CF2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -117,6 +121,10 @@ Global {97F4558A-39C5-4FD4-957F-DD501806FCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {97F4558A-39C5-4FD4-957F-DD501806FCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {97F4558A-39C5-4FD4-957F-DD501806FCFB}.Release|Any CPU.Build.0 = Release|Any CPU + {E353178F-9B75-4281-85A7-A66FF04277EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E353178F-9B75-4281-85A7-A66FF04277EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E353178F-9B75-4281-85A7-A66FF04277EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E353178F-9B75-4281-85A7-A66FF04277EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -136,6 +144,7 @@ Global {415CBC4D-1D03-48DF-990D-9131A1B26CCE} = {4138CD99-F0DC-4834-9277-F5F9EF1858B8} {DA885983-0FDE-4FB1-930A-D89907BA213C} = {3EB538FD-A7A0-49BE-BE0F-37E097BEB9CD} {97F4558A-39C5-4FD4-957F-DD501806FCFB} = {3FCA902C-521F-4634-9DC0-9D75ABDE55C5} + {E353178F-9B75-4281-85A7-A66FF04277EA} = {B98275D2-3CED-483A-AAC5-089F4CEC5CF2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {23C8BB22-2F34-4A7F-AA09-33AD01E0D3A9} diff --git a/Source/docker-compose.IntegrationTests.yml b/Source/docker-compose.IntegrationTests.yml index 7678cbe..18e2aa2 100644 --- a/Source/docker-compose.IntegrationTests.yml +++ b/Source/docker-compose.IntegrationTests.yml @@ -25,8 +25,8 @@ services: condition: service_healthy mock-data-holder-energy: - container_name: mock-data-holder-energy - image: mock-data-holder-energy + container_name: mock-data-holder-energy-for-testing + image: mock-data-holder-energy-for-testing hostname: mock-data-holder-energy ports: - "8100:8100" @@ -35,7 +35,7 @@ services: - "8105:8105" build: context: . - dockerfile: Dockerfile + dockerfile: Dockerfile.for-testing environment: - ASPNETCORE_ENVIRONMENT=Release volumes: diff --git a/mock-data-holder-discovery-architecture.png b/mock-data-holder-discovery-architecture.png new file mode 100644 index 0000000..a6cdad5 Binary files /dev/null and b/mock-data-holder-discovery-architecture.png differ