From e30d27701550e95d13564102639f24e297fcaf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=A5land?= <144004233+BouVid@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:57:47 +0200 Subject: [PATCH 1/5] fix(api): Unhandled exceptions fix (#592) * added catch for invalid operation exception to controllers where this is throwable. * removed unnecessary comment * revert chages to utillitiesController --- .../Requests/InternalRequestsController.cs | 16 +++++++++++++ .../Utilities/UtilitiesController.cs | 23 +++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs index 51bd0bc85..8d708a038 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs @@ -109,6 +109,10 @@ public async Task> CreateProjectAlloc return Created($"/projects/{projectIdentifier}/requests/{newRequest!.RequestId}", new ApiResourceAllocationRequest(newRequest)); } + catch (InvalidOperationException iv) + { + return ApiErrors.InvalidOperation(iv); + } catch (ValidationException ex) { return ApiErrors.InvalidOperation(ex); @@ -193,6 +197,10 @@ public async Task> CreateProjectAlloc // Using the requests for position endpoint as created ref.. This is not completely accurate as it could return more than those created. Best option though. return Created($"/projects/{projectIdentifier}/positions/{request.OrgPositionId}/requests", requests.Select(x => new ApiResourceAllocationRequest(x)).ToList()); } + catch (InvalidOperationException iv) + { + return ApiErrors.InvalidOperation(iv); + } catch (ValidationException ex) { return ApiErrors.InvalidOperation(ex); @@ -287,6 +295,10 @@ public async Task> CreateResourceOwne return Created($"/departments/{departmentPath}/resources/requests/{newRequest!.RequestId}", new ApiResourceAllocationRequest(newRequest)); } + catch (InvalidOperationException iv) + { + return ApiErrors.InvalidOperation(iv); + } catch (ValidationException ex) { return ApiErrors.InvalidOperation(ex); @@ -363,6 +375,10 @@ public async Task> PatchInternalReque return new ApiResourceAllocationRequest(updatedRequest!); } + catch (InvalidOperationException iv) + { + return ApiErrors.InvalidOperation(iv); + } catch (ValidationException ve) { return ApiErrors.InvalidOperation(ve); diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Utilities/UtilitiesController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Utilities/UtilitiesController.cs index 67f57a4d6..f61125f91 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Utilities/UtilitiesController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Utilities/UtilitiesController.cs @@ -22,7 +22,8 @@ public class UtilitiesController : ResourceControllerBase private readonly IFusionTokenProvider tokenProvider; private readonly IOptions fusionOptions; - public UtilitiesController(IHttpClientFactory httpClientFactory, IFusionTokenProvider tokenProvider, IOptions fusionOptions) + public UtilitiesController(IHttpClientFactory httpClientFactory, IFusionTokenProvider tokenProvider, + IOptions fusionOptions) { this.httpClientFactory = httpClientFactory; this.tokenProvider = tokenProvider; @@ -30,7 +31,8 @@ public UtilitiesController(IHttpClientFactory httpClientFactory, IFusionTokenPro } [HttpPost("/utilities/parse-spreadsheet")] - public async Task> ValidateContractorImportSpreadsheet([FromForm] ConvertSpreadsheetRequest request) + public async Task> ValidateContractorImportSpreadsheet( + [FromForm] ConvertSpreadsheetRequest request) { if (request == null) return FusionApiError.InvalidOperation("MissingBody", "Could not locate any body payload"); @@ -57,14 +59,16 @@ public async Task> ValidateContractorImportSpreads if (response.IsSuccessStatusCode) return JsonConvert.DeserializeObject(content)!; - throw new InvalidOperationException($"Parser function returned non-successfull response ({response.StatusCode})."); + throw new InvalidOperationException( + $"Parser function returned non-successfull response ({response.StatusCode})."); } [HttpGet("/utilities/templates/import-personnel")] public async Task DownloadImportPersonnelTemplate() { const string fileName = "fusion personnel import.xlsx"; - using var templateFile = Assembly.GetExecutingAssembly().GetManifestResourceStream("Fusion.Resources.Api.Data.personnel-import-template.xlsx"); + using var templateFile = Assembly.GetExecutingAssembly() + .GetManifestResourceStream("Fusion.Resources.Api.Data.personnel-import-template.xlsx"); using var memoryStream = new MemoryStream(); if (templateFile == null) @@ -117,6 +121,7 @@ public class ExcelHeader /// public int ColIndex { get; set; } } + public class ExcelDataRow { /// @@ -146,10 +151,14 @@ public class ExcelParserMessage /// public string Cell { get; set; } = null!; - public enum ExcelParserMessageLevel { Information, Warning, Error } - + public enum ExcelParserMessageLevel + { + Information, + Warning, + Error + } } #endregion } -} +} \ No newline at end of file From 92a2fff69099d6a463574cc8a043c81fafd60116 Mon Sep 17 00:00:00 2001 From: aleklundeq <138589813+aleklundeq@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:15:21 +0200 Subject: [PATCH 2/5] Changed variable and name for when getting Persons from Person API (#596) --- .../api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs b/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs index 86f7590f2..bfb9b0099 100644 --- a/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs +++ b/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs @@ -34,9 +34,9 @@ public async Task> Handle(GetPersonProfile var tasks = new List>>(); // Max number of identifiers is 500, so we chunk the requests - foreach (var req in request.Identifiers.Chunk(500)) + foreach (var identifierBatch in request.Identifiers.Chunk(500)) { - tasks.Add(profileResolver.ResolvePersonsAsync(request.Identifiers)); + tasks.Add(profileResolver.ResolvePersonsAsync(identifierBatch)); } var results = await Task.WhenAll(tasks); From 9b8e5562383d18bd755fa2737ed1d49f126c4903 Mon Sep 17 00:00:00 2001 From: aleklundeq <138589813+aleklundeq@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:55:39 +0200 Subject: [PATCH 3/5] feat: Do not set ProposedPerson = NULL when ResetWorkflow (#594) - [x] New feature - [ ] Bug fix - [ ] High impact **Description of work:** When resetting workflow as a step in rejecting a AllocationRequest I have removed functionality to set ProposedPerson to NULL. This is an attempt to make ProposedPerson available when the task owner is responding to a rejected request, which should be to create a new request. It is then easier to know who was the previous suggested resource before creating a new request **Testing:** - [x] Can be tested - [ ] Automatic tests created / updated - [x] Local tests are passing Can be tested by creating a new request with a specific resource (as proposedPerson). Then the resource owner should reject the request. The rejected request should then still contain the proposedPerson suggested in the first step. **Checklist:** - [x] Considered automated tests - [ ] ~~Considered updating specification / documentation~~ - [x] Considered work items - [ ] ~~Considered security~~ - [x] Performed developer testing - [x] Checklist finalized / ready for review --- .../Commands/Requests/InternalRequests/ResetWorkflow.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs index c5fae299c..ba7d57ba0 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs @@ -36,7 +36,6 @@ public async Task Handle(ResetWorkflow request, CancellationToken cancella requestItem.State = new DbOpState(); requestItem.IsDraft = true; requestItem.AssignedDepartment = null; - requestItem.ProposedPerson = new DbOpProposedPerson(); db.Workflows.RemoveRange( db.Workflows.Where(wf => wf.RequestId == request.requestId) From 5902c6df5ad36a87dc25dab9ba092e86f280c753 Mon Sep 17 00:00:00 2001 From: aleklundeq <138589813+aleklundeq@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:20:33 +0200 Subject: [PATCH 4/5] fix: Increased time of delay for each retry of queued message (Provision) (#595) - [ ] New feature - [x] Bug fix - [ ] High impact [AB#44549](https://statoil-proview.visualstudio.com/787035c2-8cf2-4d73-a83e-bb0e6d937eec/_workitems/edit/44549) **Description of work:** Because there have been some requests that have timed out when running its natural cause and there are not obvious exceptions we will try to increase the amount of time we delay the retry. We increase from 10 seconds * retryCount to 30 seconds * retryCount. This means that we will wait around 4 minutes before retrying the last time (fourth time). NB! The initial code did prepare scheduledTime to be increased 10 seconds for each retry, but the value was never assigned to retryMessage. We will still increase the delay to be 30 seconds for each try. **Testing:** - [x] Can be tested - [ ] Automatic tests created / updated - [x] Local tests are passing The function could be tested with natural processing of a request going through all the stages (create a new request, approve that request and then check that the request are beeing processed in provision). It is only when the provision-stage fails that we may experience the fix. **Checklist:** - [x] Considered automated tests - [ ] ~~Considered updating specification / documentation~~ - [x] Considered work items - [ ] ~~Considered security~~ - [x] Performed developer testing - [x] Checklist finalized / ready for review --- .../ServiceBus/QueueMessageProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs b/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs index 41b37c841..83280465c 100644 --- a/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs +++ b/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs @@ -61,11 +61,12 @@ public async Task ProcessWithRetriesAsync(ServiceBusReceivedMessage message, Fun { var retryMessage = new ServiceBusMessage(message); retryCount++; - var interval = 10 * retryCount; + var interval = 30 * retryCount; var scheduledTime = DateTimeOffset.Now.AddSeconds(interval); retryMessage.ApplicationProperties["retry-count"] = retryCount; retryMessage.ApplicationProperties["original-SequenceNumber"] = originalSequence; + retryMessage.ScheduledEnqueueTime = scheduledTime; await sender.AddAsync(retryMessage); await receiver.CompleteMessageAsync(message); From 8c955049c90452a72d4efbf28b3be857e4070235 Mon Sep 17 00:00:00 2001 From: Hans Dahle Date: Mon, 16 Oct 2023 13:09:01 +0200 Subject: [PATCH 5/5] Update integration lib packages & cache reset api (#597) - [x] New feature - [ ] Bug fix - [x] High impact **Description of work:** Was only going to add a simple endpoint to trigger cache reset across deployed instances. Utilizing distributed mediatr lib to coordinate across instances. This caused a larger nuget upgrade scenario, so the main workload here is related to the nuget upgrade. This also solves the discovery service issue, which promotes integration lib upgrade. MediatR lib needed to be upgraded which had breaking changes, deprecated the AsyncRequestHandler abstract type, requiring switch to IRequestHandler. This change also impacted how requests without responses were handled. Behaviours needed updates as well as generic methods which cause ambigous signature issues (DispatchCommand). **Testing:** - [x] Can be tested - [x] Automatic tests created / updated - [x] Local tests are passing Needed to update fixtures due to changes in MediatR setup logic and introduction of new background services and integration which needed new mocks. Distributed mediatr have service bus integration which requires connection string. This is mocked with services doing nothing (if we need to verify distributed message handling a new mock will need to be created). **Checklist:** - [x] Considered automated tests - [ ] Considered updating specification / documentation - [ ] Considered work items - [x] Considered security - [x] Performed developer testing - [x] Checklist finalized / ready for review Should verify cache reset endpoint, to verify that distributed infra is set up correctly. --- pipelines/function-pr-pipeline.yml | 4 +- .../templates/deploy-function-pr-template.yml | 94 +++++++++++++++++++ .../Fusion.Resources.Authorization.csproj | 2 +- .../Controllers/AdminController.cs | 55 ++++++++++- .../Departments/DepartmentsController.cs | 2 +- .../Person/PersonAbsenceController.cs | 2 +- .../Controllers/Person/PersonController.cs | 2 +- .../Personnel/InternalPersonnelController.cs | 2 +- .../Requests/InternalRequestsController.cs | 16 ++-- .../Controllers/ResourceControllerBase.cs | 2 +- .../ResponsibilityMatrixController.cs | 2 +- .../ResetCacheNotification.cs | 35 +++++++ .../Fusion.Resources.Api.csproj | 23 ++--- .../NotifyRequestCreatorHandler.cs | 4 +- .../NotifyResourceOwnerHandler.cs | 4 +- .../NotifyTaskOwnerHandler.cs | 4 +- .../api/Fusion.Resources.Api/Startup.cs | 14 ++- .../Fusion.Resources.Application.csproj | 2 +- .../Behaviours/AuditableCommandBehaviour.cs | 4 +- .../Behaviours/RequestValidationBehavior.cs | 2 +- .../Behaviours/TelemetryBehaviour.cs | 2 +- .../Departments/AddDelegatedResourceOwner.cs | 8 +- .../EmploymentStatus/DeletePersonAbsence.cs | 4 +- .../Commands/Personnel/DeletePersonNote.cs | 4 +- .../Personnel/ResetAllocationState.cs | 4 +- .../Commands/Requests/DeleteComment.cs | 4 +- .../InternalRequests/DeleteInternalRequest.cs | 4 +- .../Commands/Requests/UpdateComment.cs | 4 +- .../DeleteResponsibilityMatrix.cs | 4 +- .../IServiceCollectionExtensions.cs | 3 +- .../DomainAssemblyMarkerType.cs | 15 +++ .../Fusion.Resources.Domain.csproj | 17 ++-- .../Configuration/LogicConfigExtensions.cs | 17 ---- .../Fusion.Resources.Logic.csproj | 4 + .../LogicAssemblyMarkerType.cs | 15 +++ .../Allocation/ProvisionAllocationRequest.cs | 4 +- .../ResourceAllocationRequest/Approve.cs | 4 +- .../ResourceAllocationRequest/Initialize.cs | 4 +- .../ResourceAllocationRequest/Provision.cs | 4 +- .../QueueProvisioning.cs | 4 +- .../ProvisionResourceOwnerRequest.cs | 4 +- .../UpdateOrgPositionInstanceHaveRequest.cs | 4 +- .../Functions/ScheduledJobsFunctions.cs | 41 ++++++++ .../Fusion.Resources.Functions.csproj | 2 +- .../Fixture/ResourcesApiWebAppFactory.cs | 2 + .../FakeDistributedNotificationChannel.cs | 22 +++++ .../FakeDistributedNotificationReceiver.cs | 23 +++++ .../DbTestFixture.cs | 2 +- .../Fusion.Testing.Authentication.csproj | 3 +- .../Fusion.Testing.Core.csproj | 3 +- ...Fusion.Testing.Mocks.ContextService.csproj | 4 +- ...Fusion.Testing.Mocks.LineOrgService.csproj | 1 - .../Fusion.Testing.Mocks.OrgService.csproj | 9 +- ...Fusion.Testing.Mocks.ProfileService.csproj | 7 +- 54 files changed, 409 insertions(+), 122 deletions(-) create mode 100644 pipelines/templates/deploy-function-pr-template.yml create mode 100644 src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs create mode 100644 src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs delete mode 100644 src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs create mode 100644 src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs create mode 100644 src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs create mode 100644 src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs create mode 100644 src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs diff --git a/pipelines/function-pr-pipeline.yml b/pipelines/function-pr-pipeline.yml index 7013719a9..d4b216343 100644 --- a/pipelines/function-pr-pipeline.yml +++ b/pipelines/function-pr-pipeline.yml @@ -17,6 +17,7 @@ pr: variables: subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' webPackage: '$(Pipeline.Workspace)/drop/Fusion.Resources.Functions.zip' + prNumber: $(System.PullRequest.PullRequestNumber) stages: - template: templates/build-function.yml @@ -43,10 +44,11 @@ stages: runOnce: deploy: steps: - - template: templates/deploy-function-template.yml + - template: templates/deploy-function-pr-template.yml parameters: envName: $(environment) clientId: '5a842df8-3238-415d-b168-9f16a6a6031b' + pullRequestNumber: $(prNumber) - task: AzureFunctionApp@1 inputs: diff --git a/pipelines/templates/deploy-function-pr-template.yml b/pipelines/templates/deploy-function-pr-template.yml new file mode 100644 index 000000000..8fd90bf87 --- /dev/null +++ b/pipelines/templates/deploy-function-pr-template.yml @@ -0,0 +1,94 @@ +# +# Template for deploying pull request function app. +# +# Done this instead of making one template to include all ifs-and-buts. +# + + +parameters: + envName: '' + clientId: '' + pullRequestNumber: '' + fusionResource: '5a842df8-3238-415d-b168-9f16a6a6031b' + templateFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/function.template.json + disabledFunctionsFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/disabled-functions.json + +steps: +- checkout: self +- task: AzurePowerShell@4 + displayName: 'Deploy Function ARM template' + inputs: + azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + ScriptType: 'InlineScript' + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + # + # Hardcoded to PR env + # + $environment = "pr" + $pullRequestNumber = "${{ parameters.pullRequestNumber }}" + $fusionEnvironment = "ci" + $functionAppName = "func-fap-resources-$environment" + $envVaultName = "kv-fap-resources-$environment" + $envResourceGroup = Get-AzResourceGroup -Tag @{ "fusion-app-component" = "resources-rg-$environment" } + + if ($envResourceGroup -eq $null) { throw "Cannot locate resource group for environment '$environment'" } + + $resourceGroup = $envResourceGroup.ResourceGroupName + Write-Host "Using resource group $resourceGroup" + + # + # Generate the app settings + # + # Sett correct resources URI based on environment + $resourcesFunctionUri = "https://resources-api-pr-$pullRequestNumber.fusion-dev.net/" + + $settings = @{ + clientId = "${{ parameters.clientId }}" + secretIds = @{ + clientSecret = "https://$envVaultName.vault.azure.net:443/secrets/AzureAd--ClientSecret" + serviceBus = "https://$envVaultName.vault.azure.net:443/secrets/Connectionstrings--ServiceBus" + } + endpoints = @{ + lineorg = "https://fusion-s-lineorg-$fusionEnvironment.azurewebsites.net" + org = "https://fusion-s-org-$fusionEnvironment.azurewebsites.net" + people = "https://fusion-s-people-$fusionEnvironment.azurewebsites.net" + resources = "$resourcesFunctionUri" + notifications = "https://fusion-s-notification-$fusionEnvironment.azurewebsites.net" + context = "https://fusion-s-context-$fusionEnvironment.azurewebsites.net" + portal = "https://fusion-s-portal-$fusionEnvironment.azurewebsites.net" + } + resources = @{ + fusion = "${{ parameters.fusionResource }}" + } + queues = @{ + provisionPosition = "provision-position" + } + } + + New-AzResourceGroupDeployment -Mode Incremental -Name "resources-function" -ResourceGroupName $resourceGroup -TemplateFile "${{ parameters.templateFile }}" ` + -env-name $environment ` + -settings $settings + + $functionApp = Get-AzWebApp -ResourceGroupName $resourceGroup -Name $functionAppName + Set-AzKeyVaultAccessPolicy -VaultName $envVaultName -ResourceGroupName $resourceGroup -ObjectId $functionApp.Identity.PrincipalId -PermissionsToSecrets get + + ## Load disabled functions + $disabledFunctionConfig = ConvertFrom-Json (Get-Content "${{ parameters.disabledFunctionsFile }}" -Raw) + $disabledFunctions = $disabledFunctionConfig | where -Property environment -eq $environment | Select -expandproperty disabledFunctions + + Write-Host "Disabled functions" + $disabledFunctions + + $settings = @{} + ForEach ($kvp in $functionApp.SiteConfig.AppSettings) { + $settings[$kvp.Name] = $kvp.Value + } + + ## Mark functions as disabled + $disabledFunctions | ForEach-Object { $settings["AzureWebJobs.$_.Disabled"] = "true" } + + ## Update web app settings for function app + Set-AzWebApp -ResourceGroupName $resourceGroup -Name $functionAppName -AppSettings $settings + diff --git a/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj b/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj index 9e11373a9..3d1ea1b90 100644 --- a/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj +++ b/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs index 685bf7d59..ee8416c22 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs @@ -1,4 +1,8 @@ -using Fusion.Resources.Domain; +using Fusion.AspNetCore.FluentAuthorization; +using Fusion.Authorization; +using Fusion.Integration.Profile.Internal; +using Fusion.Resources.Domain; +using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; @@ -12,22 +16,69 @@ public class AdminController : ResourceControllerBase { private readonly IOrgUnitCache orgUnitCache; + private readonly IMediator mediator; - public AdminController(IOrgUnitCache orgUnitCache) + public AdminController(IOrgUnitCache orgUnitCache, IMediator mediator) { this.orgUnitCache = orgUnitCache; + this.mediator = mediator; } [HttpGet("admin/cache/org-units")] public async Task CleareCache() { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().FullControl().FullControlInternal(); + r.AnyOf(or => + { + or.FullControl(); + or.FullControlInternal(); + or.BeTrustedApplication(); + }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + await orgUnitCache.ClearOrgUnitCacheAsync(); return Ok(); } + + [HttpPost("admin/cache/reset-internal-cache")] + public async Task ClearInternalCache() + { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().FullControl().FullControlInternal(); + r.AnyOf(or => + { + or.FullControl(); + or.FullControlInternal(); + or.BeTrustedApplication(); + }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + + await mediator.Publish(new DistributedEvents.ResetCacheNotification()); + + return new OkObjectResult(new { message = "Cache reset has been queued for all instances."}); + } + } } \ No newline at end of file diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs index 22a2dd3a9..80059a5b2 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs @@ -166,7 +166,7 @@ public async Task> AddDelegatedResourceOw UpdatedByAzureUniqueId = User.GetAzureUniqueId() ?? User.GetApplicationId() }.WithReason(request.Reason); - await DispatchAsync(command); + await DispatchCommandAsync(command); } catch (RoleDelegationExistsError ex) diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs index fc1159ca5..ce529a4f2 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs @@ -352,7 +352,7 @@ public async Task DeletePersonAbsence([FromRoute] string personId, #endregion - await DispatchAsync(new DeletePersonAbsence(id, absenceId)); + await DispatchCommandAsync(new DeletePersonAbsence(id, absenceId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs index c14580da6..cee165dc5 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs @@ -249,7 +249,7 @@ public async Task DeletePersonalNote(string personId, Guid noteId) if (!notes.Any(n => n.Id == noteId)) return ApiErrors.NotFound("Could not locate note for user"); - await DispatchAsync(new Domain.Commands.DeletePersonNote(noteId, user.azureId)); + await DispatchCommandAsync(new Domain.Commands.DeletePersonNote(noteId, user.azureId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs index 9a2444686..b467393ee 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs @@ -270,7 +270,7 @@ public async Task ResetAllocationState(string fullDepartmentString return ApiErrors.NotFound("Could not locate allocation on person"); - await DispatchAsync(new Domain.Commands.ResetAllocationState(allocation.Project.OrgProjectId, allocation.PositionId, instanceId)); + await DispatchCommandAsync(new Domain.Commands.ResetAllocationState(allocation.Project.OrgProjectId, allocation.PositionId, instanceId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs index 8d708a038..74094d3af 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs @@ -786,7 +786,7 @@ public async Task> StartProjectReques await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -833,7 +833,7 @@ public async Task> StartResourceOwner { await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -881,7 +881,7 @@ public async Task DeleteAllocationRequest(Guid requestId) await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new DeleteInternalRequest(requestId)); + await DispatchCommandAsync(new DeleteInternalRequest(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); @@ -921,7 +921,7 @@ public async Task> ProvisionProjectAl await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var scope = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Provision(requestId) + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Provision(requestId) { ForceProvision = force }); @@ -956,7 +956,7 @@ public async Task> ApproveProjectAllo try { - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); await scope.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -995,7 +995,7 @@ public async Task> ApproveProjectAllo try { - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); await scope.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -1281,7 +1281,7 @@ public async Task> UpdateRequestComment(Guid req #endregion Authorization - await DispatchAsync(new UpdateComment(commentId, update.Content)); + await DispatchCommandAsync(new UpdateComment(commentId, update.Content)); comment = await DispatchAsync(new GetRequestComment(commentId)); return new ApiRequestComment(comment!); @@ -1321,7 +1321,7 @@ public async Task DeleteRequestComment(Guid requestId, Guid commen #endregion Authorization - await DispatchAsync(new DeleteComment(commentId)); + await DispatchCommandAsync(new DeleteComment(commentId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs b/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs index 66dd55703..8aa1aac38 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs @@ -57,7 +57,7 @@ protected Task BeginTransactionAsync() return scope.BeginTransactionAsync(); } - protected Task DispatchAsync(IRequest command) + protected Task DispatchCommandAsync(IRequest command) { var mediator = HttpContext.RequestServices.GetRequiredService(); return mediator.Send(command); diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs index cbbae1715..1f64c788c 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs @@ -226,7 +226,7 @@ public async Task DeleteResponsibilityMatrix(Guid matrixId) if (responsibilityMatrix == null) return FusionApiError.NotFound(matrixId, "Could not locate responsibility matrix"); - await DispatchAsync(new DeleteResponsibilityMatrix(matrixId)); + await DispatchCommandAsync(new DeleteResponsibilityMatrix(matrixId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs b/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs new file mode 100644 index 000000000..d0451af4b --- /dev/null +++ b/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs @@ -0,0 +1,35 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using Fusion.Integration.Profile.Internal; +using Fusion.Resources.Domain; +using MediatR; +using System.Threading; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.DistributedEvents +{ + /// + /// Trigger a reset of all internal caches. + /// + public class ResetCacheNotification : DistributedNotification + { + + public class Handler : INotificationHandler + { + private readonly IOrgUnitCache orgUnitCache; + private readonly IProfileCache fusionProfileResolverCache; + + public Handler(IOrgUnitCache orgUnitCache, IProfileCache fusionProfileResolverCache) + { + this.orgUnitCache = orgUnitCache; + this.fusionProfileResolverCache = fusionProfileResolverCache; + } + + public async Task Handle(ResetCacheNotification notification, CancellationToken cancellationToken) + { + await fusionProfileResolverCache.ClearAsync(); + await orgUnitCache.ClearOrgUnitCacheAsync(); + + } + } + } +} diff --git a/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj b/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj index c4bd24281..0fe72fd8d 100644 --- a/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj +++ b/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj @@ -16,22 +16,23 @@ - - - - - - - - - + + + + + + + + + + - - + + diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs index fa88caee5..6a210ea73 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyRequestCreatorHandler : AsyncRequestHandler + public class NotifyRequestCreatorHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyRequestCreatorHandler(IFusionNotificationClient notificationClient, this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyRequestCreator request, CancellationToken cancellationToken) + public async Task Handle(NotifyRequestCreator request, CancellationToken cancellationToken) { var allocationRequest = await GetInternalRequestAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs index 2b73c062d..26380e7f3 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyResourceOwnerHandler : AsyncRequestHandler + public class NotifyResourceOwnerHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyResourceOwnerHandler(IFusionNotificationClient notificationClient, this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyResourceOwner request, CancellationToken cancellationToken) + public async Task Handle(NotifyResourceOwner request, CancellationToken cancellationToken) { var recipients = await GenerateRecipientsAsync(request.Editor.Person.AzureUniqueId, request.AssignedDepartment); diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs index bbcb184a5..e5715a5c5 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyTaskOwnerHandler : AsyncRequestHandler + public class NotifyTaskOwnerHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyTaskOwnerHandler(IFusionNotificationClient notificationClient, IMed this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyTaskOwner request, CancellationToken cancellationToken) + public async Task Handle(NotifyTaskOwner request, CancellationToken cancellationToken) { var allocationRequest = await GetInternalRequestAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Api/Startup.cs b/src/backend/api/Fusion.Resources.Api/Startup.cs index fde80f540..7d2ce113a 100644 --- a/src/backend/api/Fusion.Resources.Api/Startup.cs +++ b/src/backend/api/Fusion.Resources.Api/Startup.cs @@ -8,6 +8,8 @@ using Fusion.Resources.Api.HostedServices; using Fusion.Resources.Api.Middleware; using Fusion.Resources.Domain; +using Fusion.Resources.Domain.Commands; +using Fusion.Resources.Logic; using JSM.FluentValidation.AspNet.AsyncFilter; using MediatR; using Microsoft.ApplicationInsights.DependencyCollector; @@ -19,6 +21,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; +using SixLabors.ImageSharp; +using System.Reflection; namespace Fusion.Resources.Api { @@ -127,11 +131,17 @@ public void ConfigureServices(IServiceCollection services) services.AddResourceDatabase(Configuration); services.AddResourceDomain(); - services.AddResourceLogic(); services.AddResourcesApplicationServices(); services.AddResourcesAuthorizationHandlers(); - services.AddMediatR(typeof(Startup)); // Add notification handlers in api project + + // Add mediatn from api, domain and logic assembly. + services.AddMediatR(c => c + .RegisterServicesFromAssemblyContaining() + .RegisterServicesFromAssemblyContaining() + .RegisterServicesFromAssemblyContaining()); + + services.AddMediatRDistributedNotification(setup => setup.ConnectionString = Configuration.GetConnectionString("ServiceBus")); services.AddHostedService(); #endregion Resource services diff --git a/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj b/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj index 01a65095a..549f0db3c 100644 --- a/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj +++ b/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs index f8f7f9502..59d6b6863 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs @@ -8,9 +8,9 @@ using System.Threading.Tasks; namespace Fusion.Resources.Domain.Behaviours -{ +{ - public class TrackableRequestBehaviour : IPipelineBehavior where TRequest : IRequest + public class TrackableRequestBehaviour : IPipelineBehavior where TRequest : notnull { private readonly IHttpContextAccessor httpContext; private readonly IProfileService profileServices; diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs index fe26f2c50..8f5f47f9c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs @@ -8,7 +8,7 @@ namespace Fusion.Resources.Domain.Behaviours { - public class RequestValidationBehavior : IPipelineBehavior where TRequest : IRequest + public class RequestValidationBehavior : IPipelineBehavior where TRequest : notnull { private readonly IEnumerable> validators; diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs index 1dc763cea..db30bb0e6 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs @@ -9,7 +9,7 @@ namespace Fusion.Resources.Domain.Behaviours { - public class TelemetryBehaviour : IPipelineBehavior where TRequest : IRequest + public class TelemetryBehaviour : IPipelineBehavior where TRequest : notnull { private readonly TelemetryClient telemetryClient; diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs index 505cb89e1..beb69fbf0 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs @@ -1,5 +1,4 @@ -using Fusion.Integration.Profile; -using Fusion.Integration.Roles; +using Fusion.Integration.Roles; using Fusion.Resources.Database; using Fusion.Resources.Database.Entities; using MediatR; @@ -7,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; namespace Fusion.Resources.Domain.Commands.Departments { @@ -44,7 +42,7 @@ public Handler(ResourcesDbContext db, IFusionRolesClient rolesClient) this.rolesClient = rolesClient; } - public async Task Handle(AddDelegatedResourceOwner request, CancellationToken cancellationToken) + public async Task Handle(AddDelegatedResourceOwner request, CancellationToken cancellationToken) { var alreadyDelegated = db.DelegatedDepartmentResponsibles.Any(x => x.ResponsibleAzureObjectId == request.ResponsibleAzureUniqueId && @@ -75,8 +73,6 @@ public async Task Handle(AddDelegatedResourceOwner request, CancellationTo db.DelegatedDepartmentResponsibles.Add(responsible); await db.SaveChangesAsync(); - - return Unit.Value; } } diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs b/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs index 93d0f3951..2664bae03 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs @@ -19,7 +19,7 @@ public DeletePersonAbsence(PersonId personId, Guid id) private PersonId PersonId { get; set; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -28,7 +28,7 @@ public Handler(ResourcesDbContext resourcesDb) this.resourcesDb = resourcesDb; } - protected override async Task Handle(DeletePersonAbsence request, CancellationToken cancellationToken) + public async Task Handle(DeletePersonAbsence request, CancellationToken cancellationToken) { var dbEntity = await resourcesDb.PersonAbsences .GetById(request.PersonId, request.Id) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs index fe6b6c27b..30fdd47e7 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs @@ -20,7 +20,7 @@ public DeletePersonNote(Guid noteId, Guid personAzureUniqueId) public Guid NoteId { get; } public Guid PersonAzureUniqueId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; @@ -29,7 +29,7 @@ public Handler(ResourcesDbContext dbContext) this.dbContext = dbContext; } - protected override async Task Handle(DeletePersonNote request, CancellationToken cancellationToken) + public async Task Handle(DeletePersonNote request, CancellationToken cancellationToken) { var note = await dbContext.PersonNotes.FirstOrDefaultAsync(n => n.Id == request.NoteId && n.AzureUniqueId == request.PersonAzureUniqueId); if (note is null) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs index 185797151..8c1607283 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs @@ -24,7 +24,7 @@ public ResetAllocationState(Guid orgProjectId, Guid orgPositionId, Guid orgInsta public Guid OrgPositionId { get; } public Guid OrgInstanceId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly ResourcesDbContext dbContext; @@ -37,7 +37,7 @@ public Handler(ILogger logger, ResourcesDbContext dbContext, IOrgApiCli this.orgApiClientFactory = orgApiClientFactory; } - protected override async Task Handle(ResetAllocationState request, CancellationToken cancellationToken) + public async Task Handle(ResetAllocationState request, CancellationToken cancellationToken) { var client = orgApiClientFactory.CreateClient(ApiClientMode.Application); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs index 616cc0db3..dbdf83d6c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs @@ -16,7 +16,7 @@ public DeleteComment(Guid commentId) public Guid CommentId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext db; @@ -25,7 +25,7 @@ public Handler(ResourcesDbContext db) this.db = db; } - protected override async Task Handle(DeleteComment command, CancellationToken cancellationToken) + public async Task Handle(DeleteComment command, CancellationToken cancellationToken) { var comment = await db.RequestComments.FirstOrDefaultAsync(c => c.Id == command.CommentId); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs index 71dad5e8d..00886b407 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs @@ -19,7 +19,7 @@ public DeleteInternalRequest(Guid requestId) private Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; private readonly IMediator mediator; @@ -30,7 +30,7 @@ public Handler(ResourcesDbContext dbContext, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(DeleteInternalRequest request, CancellationToken ct) + public async Task Handle(DeleteInternalRequest request, CancellationToken ct) { var req = await dbContext.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs index af191959c..bff7efadb 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs @@ -18,7 +18,7 @@ public UpdateComment(Guid commentId, string content) public Guid CommentId { get; } public string Content { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext db; @@ -27,7 +27,7 @@ public Handler(ResourcesDbContext db) this.db = db; } - protected override async Task Handle(UpdateComment request, CancellationToken cancellationToken) + public async Task Handle(UpdateComment request, CancellationToken cancellationToken) { var comment = await db.RequestComments.FirstOrDefaultAsync(c => c.Id == request.CommentId); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs b/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs index f986244ff..62dbcd86c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs @@ -17,7 +17,7 @@ public DeleteResponsibilityMatrix(Guid id) private Guid Id { get; set; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -26,7 +26,7 @@ public Handler(ResourcesDbContext resourcesDb) this.resourcesDb = resourcesDb; } - protected override async Task Handle(DeleteResponsibilityMatrix request, CancellationToken cancellationToken) + public async Task Handle(DeleteResponsibilityMatrix request, CancellationToken cancellationToken) { var dbEntity = await resourcesDb.ResponsibilityMatrices .FirstOrDefaultAsync(x=>x.Id==request.Id); diff --git a/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs b/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs index c1bda93be..054d8b22b 100644 --- a/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs +++ b/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs @@ -12,8 +12,7 @@ public static class DomainConfigExtensions { public static IServiceCollection AddResourceDomain(this IServiceCollection services) - { - services.AddMediatR(typeof(DomainConfigExtensions)); + { services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TrackableRequestBehaviour<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TelemetryBehaviour<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>)); diff --git a/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs b/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs new file mode 100644 index 000000000..b7a6eeb29 --- /dev/null +++ b/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Domain +{ + /// + /// Type to easily target the assembly. + /// + public class DomainAssemblyMarkerType + { + } +} diff --git a/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj b/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj index c71b14437..5874357c3 100644 --- a/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj +++ b/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj @@ -7,15 +7,14 @@ - - - - - - - - - + + + + + + + + diff --git a/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs b/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs deleted file mode 100644 index 1fd004db6..000000000 --- a/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MediatR; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class LogicConfigExtensions - { - public static IServiceCollection AddResourceLogic(this IServiceCollection services) - { - services.AddMediatR(typeof(LogicConfigExtensions)); - - return services; - } - } -} diff --git a/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj b/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj index 9c8cc338d..41fc1e53a 100644 --- a/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj +++ b/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj @@ -11,5 +11,9 @@ + + + + diff --git a/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs b/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs new file mode 100644 index 000000000..5d7e1e21e --- /dev/null +++ b/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Logic +{ + /// + /// Type to allow easy loading of the assembly. + /// + public class LogicAssemblyMarkerType + { + } +} diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs index 3253ef68c..664d8f046 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs @@ -25,7 +25,7 @@ public ProvisionAllocationRequest(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private IOrgApiClient client; private ResourcesDbContext resourcesDb; @@ -36,7 +36,7 @@ public Handler(ResourcesDbContext resourcesDb, IOrgApiClientFactory orgApiClient this.resourcesDb = resourcesDb; } - protected override async Task Handle(ProvisionAllocationRequest request, CancellationToken cancellationToken) + public async Task Handle(ProvisionAllocationRequest request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs index e913a4fc6..deb8201ff 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs @@ -21,7 +21,7 @@ public Approve(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; private readonly IMediator mediator; @@ -32,7 +32,7 @@ public Handler(ResourcesDbContext dbContext, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(Approve request, CancellationToken cancellationToken) + public async Task Handle(Approve request, CancellationToken cancellationToken) { var dbRequest = await dbContext.ResourceAllocationRequests.FirstOrDefaultAsync(r => r.Id == request.RequestId, cancellationToken); if (dbRequest is null) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs index 9280b4bd8..206f39a19 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs @@ -25,7 +25,7 @@ public Initialize(Guid requestId) - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -37,7 +37,7 @@ public Handler(ResourcesDbContext resourcesDb, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(Initialize request, CancellationToken cancellationToken) + public async Task Handle(Initialize request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests.FirstOrDefaultAsync(r => r.Id == request.RequestId); if (dbRequest is null) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs index 3de76dd77..0735c8c4b 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs @@ -67,7 +67,7 @@ public Validator(ResourcesDbContext db, IProjectOrgResolver projectOrgResolver, } } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly ResourcesDbContext resourcesDb; @@ -80,7 +80,7 @@ public Handler(ILogger logger, ResourcesDbContext resourcesDb, IMediato this.mediator = mediator; } - protected override async Task Handle(Provision request, CancellationToken cancellationToken) + public async Task Handle(Provision request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs index 1c34299b7..96037b3ae 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs @@ -18,7 +18,7 @@ public QueueProvisioning(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly int FixedDelayInSecondsBeforeProvisioning = 5; private readonly ResourcesDbContext dbContext; @@ -30,7 +30,7 @@ public Handler(ResourcesDbContext dbContext, IQueueSender queueSender) this.queueSender = queueSender; } - protected override async Task Handle(QueueProvisioning request, CancellationToken cancellationToken) + public async Task Handle(QueueProvisioning request, CancellationToken cancellationToken) { var dbRequest = await dbContext.ResourceAllocationRequests.FindAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs index 31421ba84..0322019b8 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs @@ -26,7 +26,7 @@ public ProvisionResourceOwnerRequest(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly IOrgApiClient client; private readonly ResourcesDbContext resourcesDb; @@ -37,7 +37,7 @@ public Handler(ResourcesDbContext resourcesDb, IOrgApiClientFactory orgApiClient this.resourcesDb = resourcesDb; } - protected override async Task Handle(ProvisionResourceOwnerRequest request, CancellationToken cancellationToken) + public async Task Handle(ProvisionResourceOwnerRequest request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs index 4f2ea8bb0..2d4f56166 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs @@ -25,7 +25,7 @@ public UpdateOrgPositionInstanceHaveRequest(Guid orgProjectId, Guid orgPositionI public Guid OrgPositionInstanceId { get; } public bool HaveRequest { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly IOrgApiClient client; @@ -36,7 +36,7 @@ public Handler(IOrgApiClientFactory orgApiClientFactory, ILogger logger this.client = orgApiClientFactory.CreateClient(ApiClientMode.Application); } - protected override async Task Handle(UpdateOrgPositionInstanceHaveRequest request, CancellationToken cancellationToken) + public async Task Handle(UpdateOrgPositionInstanceHaveRequest request, CancellationToken cancellationToken) { // This command tries to update an existing position instance. If instance is not found, it may have been deleted in ORG service. // If unable to update instance, log error and proceed. diff --git a/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs b/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs new file mode 100644 index 000000000..add674152 --- /dev/null +++ b/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs @@ -0,0 +1,41 @@ +using Fusion.Resources.Functions.ApiClients; +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Functions.Functions +{ + public class ScheduledJobsFunctions + { + private readonly ILogger logger; + private HttpClient resourcesApiClient; + + public ScheduledJobsFunctions(ILogger logger, IHttpClientFactory httpClientFactory) + { + resourcesApiClient = httpClientFactory.CreateClient(HttpClientNames.Application.Resources); + this.logger = logger; + } + + [Singleton] + [FunctionName("clear-api-internal-cache")] + public async Task ReassignResourceAllocationRequestsWithInvalidDepartment([TimerTrigger("0 0 4 * * 0", RunOnStartup = false)] TimerInfo timer) + { + + var resp = await resourcesApiClient.PostAsync("/admin/cache/reset-internal-cache", null); + var result = await resp.Content.ReadAsStringAsync(); + + if (!resp.IsSuccessStatusCode) + { + logger.LogError($"Error triggering cache reset. Response from service: {result}"); + } + + resp.EnsureSuccessStatusCode(); + + } + } +} diff --git a/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj b/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj index 35816baa9..733b3b421 100644 --- a/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj +++ b/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj @@ -5,7 +5,7 @@ <_FunctionsSkipCleanOutput>true - + diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs b/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs index 4e7e1ffa9..89d5d1706 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs @@ -143,6 +143,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) return clientFactoryMock.Object; }); + services.AddSingletonIfFound(); + services.AddSingletonIfFound(); }); } } diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs new file mode 100644 index 000000000..f91fa6275 --- /dev/null +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs @@ -0,0 +1,22 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.Tests.FusionMocks +{ + /// + /// Fusion.Infrastructure.MediatR. + /// Mock type for distributed mediatr implementation. + /// + public class FakeDistributedNotificationChannel : IDistributedNotificationChannel + { + public List Notifications = new List(); + + public Task Publish(T notification) where T : IDistributedNotification + { + Notifications.Add(notification); + + return Task.CompletedTask; + } + } +} diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs new file mode 100644 index 000000000..b51d6ddfd --- /dev/null +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs @@ -0,0 +1,23 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using System.Threading; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.Tests.FusionMocks +{ + /// + /// Fusion.Infrastructure.MediatR. + /// Mock type for distributed mediatr implementation. + /// + public class FakeDistributedNotificationReceiver : IDistributedNotificationReceiver + { + public Task StartAsync(CancellationToken stoppingToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs b/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs index d6251449d..998ffef40 100644 --- a/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs +++ b/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs @@ -34,7 +34,7 @@ public DbTestFixture() protected virtual void ConfigureServices(ServiceCollection services) { - services.AddMediatR(typeof(AddSecondOpinion).Assembly); + services.AddMediatR(c => c.RegisterServicesFromAssemblyContaining()); services.AddDbContext(opts => opts.UseInMemoryDatabase($"unit-test-db-{Guid.NewGuid()}")); var profileService = new Mock(); diff --git a/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj b/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj index 09551d9d4..8b5afd679 100644 --- a/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj +++ b/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj @@ -6,9 +6,8 @@ - - + diff --git a/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj b/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj index 4d64f214c..757c53a83 100644 --- a/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj +++ b/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj @@ -8,9 +8,8 @@ - - + diff --git a/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj b/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj index 2008843af..00ae56285 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj @@ -5,10 +5,10 @@ - + - + diff --git a/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj b/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj index a1b56bda6..cd2437a53 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj @@ -9,7 +9,6 @@ - diff --git a/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj b/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj index 77773bf97..7075a7d15 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj @@ -14,13 +14,12 @@ - - - + + - - + + diff --git a/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj b/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj index b01ff9ac5..1899de46e 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj @@ -7,12 +7,11 @@ - - + + - - +