From 7841b3515a8247a14997466557f0da7c7172dd0d Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 4 Jun 2025 09:18:33 +0100 Subject: [PATCH 01/83] Admin UI and WebUI update to consume the Open API endpoints --- .../Configuration/WebSettings.cs | 5 + .../Helpers/IOpenApiFacade.cs | 59 ++ .../Helpers/OpenApiFacade.cs | 241 +++++ .../Interfaces/IOpenApiHttpClient.cs | 17 + .../Services/BaseService.cs | 21 + .../Services/CatalogueService.cs | 8 +- .../Services/ContentService.cs | 48 +- .../Services/EventService.cs | 7 +- .../Services/HierarchyService.cs | 8 +- .../Services/OpenApiHttpClient.cs | 39 + .../Services/ProviderService.cs | 9 +- .../Services/ResourceService.cs | 29 +- .../Services/UserGroupService.cs | 24 +- .../Services/UserService.cs | 13 +- .../LearningHub.Nhs.AdminUI/appsettings.json | 1 + .../Configuration/Settings.cs | 5 + .../Helpers/IOpenApiFacade.cs | 59 ++ .../Helpers/OpenApiFacade.cs | 241 +++++ .../Interfaces/IOpenApiHttpClient.cs | 17 + .../Services/ActivityService.cs | 5 +- LearningHub.Nhs.WebUI/Services/BaseService.cs | 19 + .../Services/BoomarkService.cs | 15 +- LearningHub.Nhs.WebUI/Services/CardService.cs | 5 +- .../Services/CatalogueService.cs | 7 +- .../Services/ContentService.cs | 9 +- .../Services/ContributeService.cs | 27 +- .../Services/CountryService.cs | 4 +- .../Services/DashboardService.cs | 11 +- .../Services/DetectJsLogService.cs | 5 +- .../Services/GradeService.cs | 4 +- .../Services/HierarchyService.cs | 12 +- .../Services/InternalSystemService.cs | 5 +- .../Services/JobRoleService.cs | 4 +- .../Services/LocationService.cs | 4 +- .../Services/LoginWizardService.cs | 4 +- .../Services/MyLearningService.cs | 11 +- .../Services/NotificationService.cs | 17 +- .../Services/OpenApiHttpClient.cs | 41 + .../Services/PartialFileUploadService.cs | 4 +- .../Services/ProviderService.cs | 5 +- .../Services/RatingService.cs | 5 +- .../Services/RegionService.cs | 5 +- .../Services/ResourceService.cs | 85 +- .../Services/RoadMapService.cs | 5 +- LearningHub.Nhs.WebUI/Services/RoleService.cs | 5 +- .../Services/SearchService.cs | 27 +- .../Services/SpecialtyService.cs | 4 +- .../Services/TermsAndConditionsService.cs | 4 +- .../Services/UserGroupService.cs | 4 +- LearningHub.Nhs.WebUI/Services/UserService.cs | 4 +- .../Startup/ServiceMappings.cs | 10 + LearningHub.Nhs.WebUI/appsettings.json | 199 ++-- .../Configuration/FindwiseConfig.cs | 10 + .../Configuration/LearningHubConfig.cs | 12 + .../IMediaResourcePlayedSegmentRepository.cs | 21 + .../Analytics/IEventRepository.cs | 18 + .../Repositories/Content/IPageRepository.cs | 43 + .../Content/IPageSectionDetailRepository.cs | 51 + .../Content/IPageSectionRepository.cs | 84 ++ .../Content/IVideoAssetRepository.cs | 25 + .../Repositories/IEventLogRepository.cs | 22 + .../Repositories/IResourceRepository.cs | 11 + .../Repositories/IScopeRepository.cs | 25 + .../IUserGroupAttributeRepository.cs | 26 + .../Repositories/IUserGroupRepository.cs | 41 + .../Repositories/IUserProviderRepository.cs | 34 + .../IResourceVersionEventRepository.cs | 34 + .../EntityFramework/LearningHubDbContext.cs | 5 + .../EntityFramework/ServiceMappings.cs | 2 + .../Map/Analytics/EventMap.cs | 3 +- .../Map/UserProviderMap.cs | 41 + .../MediaResourcePlayedSegmentRepository.cs | 42 + .../Repositories/Analytics/EventRepository.cs | 43 + .../Repositories/Content/PageRepository.cs | 233 +++++ .../Content/PageSectionDetailRepository.cs | 127 +++ .../Content/PageSectionRepository.cs | 546 ++++++++++ .../Content/VideoAssetRepository.cs | 45 + .../Repositories/EventLogRepository.cs | 80 ++ .../Repositories/ResourceRepository.cs | 16 + .../ResourceVersionEventRepository.cs | 51 + .../Repositories/ScopeRepository.cs | 46 + .../UserGroupAttributeRepository.cs | 46 + .../Repositories/UserGroupRepository.cs | 99 ++ .../Repositories/UserProviderRepository.cs | 88 ++ .../Startup.cs | 20 + .../Services/IBookmarkService.cs | 9 + .../Services/ICatalogueService.cs | 15 + .../Services/IDashboardService.cs | 38 + .../Services/IEventLogService.cs | 16 + .../Services/IEventService.cs | 35 + .../Services/IMyLearningService.cs | 49 + .../Services/IPageService.cs | 175 ++++ .../Services/IResourceReferenceService.cs | 18 + .../Services/IResourceService.cs | 92 +- .../Services/ISearchService.cs | 131 +++ .../Services/IUserGroupService.cs | 160 +++ .../Services/IUserLearningRecordService.cs | 24 + .../Services/IUserProviderService.cs | 19 + .../Extensions/PageExtension.cs | 92 ++ .../Helpers/BinaryFormatterHelper.cs | 21 + .../Services/BookmarkService.cs | 28 +- .../Services/CatalogueService.cs | 41 +- .../Services/DashboardService.cs | 140 +++ .../Services/EventLogService.cs | 45 + .../Services/EventService.cs | 99 ++ .../Services/MyLearningService.cs | 507 ++++++++++ .../Services/PageService.cs | 613 ++++++++++++ .../Services/ResourceReferenceService.cs | 30 + .../Services/ResourceService.cs | 595 ++++++++++- .../Services/SearchService.cs | 536 +++++++++- .../Services/UserGroupService.cs | 938 ++++++++++++++++++ .../Services/UserLearningRecordService.cs | 129 +++ .../Services/UserProviderService.cs | 52 + .../Startup.cs | 12 +- .../Services/Services/ResourceServiceTests.cs | 7 +- .../Services/Services/SearchServiceTests.cs | 11 +- .../Controllers/BookmarkController.cs | 14 + .../Controllers/ContentController.cs | 331 ++++++ .../Controllers/DashboardController.cs | 72 ++ .../Controllers/MyLearningController.cs | 86 ++ .../Controllers/ProviderController.cs | 37 +- .../Controllers/ResourceController.cs | 131 ++- .../Controllers/SearchController.cs | 356 +++++++ .../Controllers/UserController.cs | 23 +- .../Controllers/UserGroupController.cs | 380 +++++++ .../UserLearningRecordController.cs | 67 ++ .../Controllers/UserNotificationController.cs | 143 +++ 127 files changed, 9399 insertions(+), 334 deletions(-) create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Helpers/IOpenApiFacade.cs create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Helpers/OpenApiFacade.cs create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IOpenApiHttpClient.cs create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Services/OpenApiHttpClient.cs create mode 100644 LearningHub.Nhs.WebUI/Helpers/IOpenApiFacade.cs create mode 100644 LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs create mode 100644 LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs create mode 100644 LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourcePlayedSegmentRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Analytics/IEventRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionDetailRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IVideoAssetRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEventLogRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IScopeRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupAttributeRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProviderRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionEventRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserProviderMap.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourcePlayedSegmentRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Analytics/EventRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/VideoAssetRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EventLogRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionEventRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ScopeRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupAttributeRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserProviderRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventLogService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IPageService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceReferenceService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserLearningRecordService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserProviderService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Extensions/PageExtension.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/BinaryFormatterHelper.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventLogService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/PageService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceReferenceService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserLearningRecordService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProviderService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserGroupController.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserLearningRecordController.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserNotificationController.cs diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs b/AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs index f8e3c1565..5c8ab554f 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs @@ -27,6 +27,11 @@ public class WebSettings /// public string LearningHubApiUrl { get; set; } + /// + /// Gets or sets the OpenApiUrl. + /// + public string OpenApiUrl { get; set; } + /// /// Gets or sets the user api url. /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/IOpenApiFacade.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/IOpenApiFacade.cs new file mode 100644 index 000000000..2bcded72b --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/IOpenApiFacade.cs @@ -0,0 +1,59 @@ +namespace LearningHub.Nhs.AdminUI.Helpers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + + /// + /// Defines the . + /// + public interface IOpenApiFacade + { + /// + /// The GetAsync. + /// + /// The type. + /// The url. + /// The . + Task GetAsync(string url) + where T : class, new(); + + /// + /// The PostAsync. + /// + /// The type. + /// The url. + /// The body. + /// The . + Task PostAsync(string url, T body) + where T : class, new(); + + /// + /// The PostAsync. + /// + /// The type. + /// . + /// The url. + /// The body. + /// The . + Task PostAsync(string url, TBody body) + where T : class, new() + where TBody : class, new(); + + /// + /// The PutAsync. + /// + /// The url. + /// The . + Task PutAsync(string url); + + /// + /// The PutAsync. + /// + /// . + /// The url. + /// The body. + /// The . + Task PutAsync(string url, T body) + where T : class, new(); + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/OpenApiFacade.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/OpenApiFacade.cs new file mode 100644 index 000000000..6169d8e6b --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/OpenApiFacade.cs @@ -0,0 +1,241 @@ +namespace LearningHub.Nhs.AdminUI.Helpers +{ + using System; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using LearningHub.Nhs.AdminUI.Interfaces; + using LearningHub.Nhs.Models.Common; + using Newtonsoft.Json; + + /// + /// Defines the . + /// + public class OpenApiFacade : IOpenApiFacade + { + /// + /// Defines the _client. + /// + private readonly IOpenApiHttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// The client. + public OpenApiFacade(IOpenApiHttpClient client) + { + this.client = client; + } + + /// + /// The GetAsync. + /// + /// . + /// The url. + /// The . + public async Task GetAsync(string url) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var vm = new T(); + + var response = await client.GetAsync(url).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + vm = JsonConvert.DeserializeObject(result); + + return vm; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PostAsync. + /// + /// . + /// The url. + /// The body. + /// The . + public async Task PostAsync(string url, T body) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PostAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + return; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PostAsync. + /// + /// The return type. + /// The type of body parameter. + /// The url. + /// The body. + /// The . + public async Task PostAsync(string url, TBody body) + where TBody : class, new() + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var vm = new T(); + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PostAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PostAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PutAsync. + /// + /// The url. + /// The . + public async Task PutAsync(string url) + { + var client = await this.client.GetClientAsync(); + + var response = await client.PutAsync(url, null).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PutAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PutAsync. + /// + /// . + /// The url. + /// The body. + /// The . + public async Task PutAsync(string url, T body) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PutAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PutAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IOpenApiHttpClient.cs b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IOpenApiHttpClient.cs new file mode 100644 index 000000000..34c488767 --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IOpenApiHttpClient.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.AdminUI.Interfaces +{ + using System.Net.Http; + using System.Threading.Tasks; + + /// + /// The OpenApiHttpClient interface. + /// + public interface IOpenApiHttpClient + { + /// + /// The get client. + /// + /// The . + Task GetClientAsync(); + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/BaseService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/BaseService.cs index ab2a377e8..c164b5691 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/BaseService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/BaseService.cs @@ -12,6 +12,11 @@ public class BaseService /// private ILearningHubHttpClient learningHubHttpClient; + /// + /// Defines the openApiHttpClient. + /// + private IOpenApiHttpClient openApiHttpClient; + /// /// Initializes a new instance of the class. /// @@ -21,9 +26,25 @@ protected BaseService(ILearningHubHttpClient learningHubHttpClient) this.learningHubHttpClient = learningHubHttpClient; } + /// + /// Initializes a new instance of the class. + /// + /// The learningHubHttpClient. + /// The openApiHttpClient. + protected BaseService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient) + { + this.learningHubHttpClient = learningHubHttpClient; + this.openApiHttpClient = openApiHttpClient; + } + /// /// Gets the LearningHubHttpClient. /// protected ILearningHubHttpClient LearningHubHttpClient => this.learningHubHttpClient; + + /// + /// Gets the OpenApiHttpClient. + /// + protected IOpenApiHttpClient OpenApiHttpClient => this.openApiHttpClient; } } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs index 1fa44df2b..b2a082dc6 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs @@ -19,19 +19,19 @@ public class CatalogueService : BaseService, ICatalogueService /// /// Defines the _facade. /// - private readonly ILearningHubApiFacade facade; + private readonly IOpenApiFacade facade; /// /// Initializes a new instance of the class. /// /// The learningHubHttpClient. - /// The learningHubApiFacade. + /// The openApiFacade. public CatalogueService( ILearningHubHttpClient learningHubHttpClient, - ILearningHubApiFacade learningHubApiFacade) + IOpenApiFacade openApiFacade) : base(learningHubHttpClient) { - this.facade = learningHubApiFacade; + this.facade = openApiFacade; } /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs index d49cd8422..cc792c228 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs @@ -27,14 +27,16 @@ public class ContentService : BaseService, IContentService /// private readonly IFileService fileService; private readonly IAzureMediaService azureMediaService; + private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The openApiHttpClient. /// The fileService. /// azureMediaService. - public ContentService(ILearningHubHttpClient learningHubHttpClient, IFileService fileService, IAzureMediaService azureMediaService) + public ContentService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IFileService fileService, IAzureMediaService azureMediaService) : base(learningHubHttpClient) { this.fileService = fileService; @@ -48,7 +50,7 @@ public ContentService(ILearningHubHttpClient learningHubHttpClient, IFileService /// The . public async Task DiscardAsync(int pageId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/discard/{pageId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -68,7 +70,7 @@ public async Task GetPageByIdAsync(int id, bool includeHidden = f { PageViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = !includeHidden ? $"content/page/{id}" : $"content/page-all/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -97,7 +99,7 @@ public async Task GetPageSectionDetailByIdAsync(int { PageSectionDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/page-section-detail/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -126,7 +128,7 @@ public async Task GetEditablePageSectionDetailByIdAs { PageSectionDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/editable-page-section-detail/{pageSectionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -154,7 +156,7 @@ public async Task GetPagesAsync() { PageResultViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/pages"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -181,7 +183,7 @@ public async Task GetPagesAsync() /// The . public async Task PublishAsync(int pageId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/publish/{pageId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -214,7 +216,7 @@ public async Task UpdatePageImageSectionDetailAsync(int pageId, PageImageSection var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/page-image-section-detail/{pageId}"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -232,7 +234,7 @@ public async Task UpdatePageImageSectionDetailAsync(int pageId, PageImageSection /// The . public async Task ChangeOrderAsync(UpdatePageSectionOrderModel requestViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "content/change-order/"; var content = new StringContent( JsonConvert.SerializeObject(requestViewModel), @@ -259,7 +261,7 @@ public async Task ChangeOrderAsync(UpdatePageSectionOrderModel requestViewModel) /// The . public async Task CloneAsync(int pageSectionId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/clone/{pageSectionId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -276,7 +278,7 @@ public async Task CloneAsync(int pageSectionId) /// The . public async Task HideAsync(int pageSectionId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/hide/{pageSectionId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -293,7 +295,7 @@ public async Task HideAsync(int pageSectionId) /// The . public async Task UnHideAsync(int pageSectionId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/unhide/{pageSectionId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -310,7 +312,7 @@ public async Task UnHideAsync(int pageSectionId) /// The . public async Task DeleteAsync(int pageSectionId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/delete/{pageSectionId}"; var response = await client.PutAsync(request, null).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || @@ -376,7 +378,7 @@ public async Task> GetFileTypeAsync() { List fileTypeList = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetFileTypes"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -459,7 +461,7 @@ public async Task SaveAttributeFileDetailsAsync(FileCreateRequestViewModel var json = JsonConvert.SerializeObject(fileCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/save-attribute-file"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -493,7 +495,7 @@ public async Task SaveVideoAssetAsync(FileCreateRequestViewModel fileCreate var json = JsonConvert.SerializeObject(fileCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/save-video-asset"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -593,7 +595,7 @@ public async Task GetFileChunkDetail(int fileChunkDeta { FileChunkDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetFileChunkDetail/{fileChunkDetailId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -624,7 +626,7 @@ public async Task SaveFileChunkDetailsAsync(FileChunkDetailViewModel fileCh var json = JsonConvert.SerializeObject(fileChunkDetailCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SaveFileChunkDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -682,7 +684,7 @@ public async Task RegisterChunkedFileAsync(FileChunkRegisterMo public async Task DeleteFileChunkDetailAsync(int fileChunkDetailId) { ApiResponse apiResponse = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteFileChunkDetail/{fileChunkDetailId}"; var response = await client.DeleteAsync(request).ConfigureAwait(false); @@ -814,7 +816,7 @@ public async Task UpdateVideoAssetAsync(VideoAssetViewModel model) var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/update-video-asset"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -844,7 +846,7 @@ public async Task UpdateVideoAssetAsync(VideoAssetViewModel model) /// The . public async Task CreatePageSectionAsync(PageSectionViewModel requestViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "content/create-page-section/"; var content = new StringContent( JsonConvert.SerializeObject(requestViewModel), @@ -874,7 +876,7 @@ public async Task CreatePageSectionAsync(PageSectionViewModel requestViewMo /// The . public async Task UpdatePageSectionDetailAsync(PageSectionDetailViewModel updateViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "content/update-page-section-detail/"; var content = new StringContent( JsonConvert.SerializeObject(updateViewModel), @@ -899,7 +901,7 @@ public async Task GetPageSectionDetailVideoAssetById { PageSectionDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"content/page-section-detail-video/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/EventService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/EventService.cs index 000fce940..db61e47e3 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/EventService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/EventService.cs @@ -18,8 +18,9 @@ public class EventService : BaseService, IEventService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. - public EventService(ILearningHubHttpClient learningHubHttpClient) - : base(learningHubHttpClient) + /// The openApiHttpClient. + public EventService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { } @@ -30,7 +31,7 @@ public async Task Create(Event eventEntity) var json = JsonConvert.SerializeObject(eventEntity); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Event/Create"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/HierarchyService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/HierarchyService.cs index b408cb637..17b37999b 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/HierarchyService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/HierarchyService.cs @@ -16,21 +16,21 @@ public class HierarchyService : BaseService, IHierarchyService /// /// Defines the _facade. /// - private readonly ILearningHubApiFacade facade; + private readonly IOpenApiFacade facade; /// /// Initializes a new instance of the class. /// /// The learning hub http client. - /// The learningHubApiFacade. + /// The openApiFacade. /// The logger. public HierarchyService( ILearningHubHttpClient learningHubHttpClient, - ILearningHubApiFacade learningHubApiFacade, + IOpenApiFacade openApiFacade, ILogger logger) : base(learningHubHttpClient) { - this.facade = learningHubApiFacade; + this.facade = openApiFacade; } /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/OpenApiHttpClient.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/OpenApiHttpClient.cs new file mode 100644 index 000000000..5e8463cad --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/OpenApiHttpClient.cs @@ -0,0 +1,39 @@ +namespace LearningHub.Nhs.AdminUI.Services +{ + using System.Net.Http; + using LearningHub.Nhs.AdminUI.Configuration; + using LearningHub.Nhs.AdminUI.Interfaces; + using LearningHub.Nhs.Caching; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + + /// + /// The open api http client. + /// + public class OpenApiHttpClient : BaseHttpClient, IOpenApiHttpClient + { + /// + /// Initializes a new instance of the class. + /// + /// The http context accessor. + /// The web settings. + /// The http client. + /// The logger. + /// The cache service. + public OpenApiHttpClient( + IHttpContextAccessor httpContextAccessor, + IOptions webSettings, + HttpClient client, + ILogger logger, + ICacheService cacheService) + : base(httpContextAccessor, webSettings.Value, client, logger, cacheService) + { + } + + /// + /// Gets the open api url. + /// + public override string ApiUrl => this.WebSettings.OpenApiUrl; + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/ProviderService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/ProviderService.cs index eeb6976f9..cfdc4050b 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/ProviderService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/ProviderService.cs @@ -24,19 +24,20 @@ public class ProviderService : BaseService, IProviderService /// /// Defines the _facade. /// - private readonly ILearningHubApiFacade facade; + private readonly IOpenApiFacade facade; /// /// Initializes a new instance of the class. /// /// The cache service. /// Learning hub http client. - /// The learningHubApiFacade. - public ProviderService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, ILearningHubApiFacade learningHubApiFacade) + /// open api http client. + /// The openApiFacade. + public ProviderService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IOpenApiFacade openApiFacade) : base(learningHubHttpClient) { this.cacheService = cacheService; - this.facade = learningHubApiFacade; + this.facade = openApiFacade; } /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/ResourceService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/ResourceService.cs index 4c6e3fe46..038681c64 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/ResourceService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/ResourceService.cs @@ -24,8 +24,9 @@ public class ResourceService : BaseService, IResourceService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. - public ResourceService(ILearningHubHttpClient learningHubHttpClient) - : base(learningHubHttpClient) + /// The openApiHttpClient. + public ResourceService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { } @@ -50,7 +51,7 @@ public async Task> GetResourc var filter = JsonConvert.SerializeObject(pagingRequestModel.Filter); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(pagingRequestModel); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); @@ -82,7 +83,7 @@ public async Task> GetResourceVersionEventsA { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionEvents/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -111,7 +112,7 @@ public async Task GetResourceVersionVa { ResourceVersionValidationResultViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionValidationResult/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -140,7 +141,7 @@ public async Task GetResourceVersionDevIdDetailsA { ResourceVersionDevIdViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionDevIdDetails/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -167,7 +168,7 @@ public async Task GetResourceVersionDevIdDetailsA /// The . public async Task DoesDevIdExistsAsync(string devId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DoesDevIdExists/{devId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -198,7 +199,7 @@ public async Task UpdateDevIdDetailsAsync(ResourceVersionDevIdViewModel model) var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateDevId"; var response = await client.PutAsync(request, stringContent).ConfigureAwait(false); @@ -223,7 +224,7 @@ public async Task GetResourceVersionExtendedVi { ResourceVersionExtendedViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionExtendedViewModel/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -252,7 +253,7 @@ public async Task> GetResourceVersionsAsync(int r { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersions/{resourceId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -283,7 +284,7 @@ public async Task RevertToDraft(int resourceVersion var json = JsonConvert.SerializeObject(new { resourceVersionId }); var stringContent = new StringContent(resourceVersionId.ToString(), Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/RevertToDraft"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -335,7 +336,7 @@ public async Task TransferResourceOwnership(int res var json = JsonConvert.SerializeObject(vm); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/TransferResourceOwnership"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -382,7 +383,7 @@ public async Task UnpublishResourceVersionAsync(int var json = JsonConvert.SerializeObject(vm); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UnpublishResourceVersion"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -433,7 +434,7 @@ public async Task CreateResourceVersionEvent(int re var json = JsonConvert.SerializeObject(vm); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/CreateResourceVersionEvent"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs index 45dff59de..a9a7a3133 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs @@ -112,7 +112,7 @@ public async Task GetUserGroupAdminDetailbyIdAsyn { UserGroupAdminDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserGroupAdminDetailById/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -141,7 +141,7 @@ public async Task> GetUserGroupAdminRoleDetailByIdA { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserGroupAdminRoleDetailById/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -171,7 +171,7 @@ public async Task CreateUserGroup(UserGroupAdminDet var json = JsonConvert.SerializeObject(userGroup); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/CreateUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -214,7 +214,7 @@ public async Task UpdateUserGroup(UserGroupAdminDet var json = JsonConvert.SerializeObject(userGroup); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/UpdateUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -258,7 +258,7 @@ public async Task DeleteUserGroup(int userGroupId) var json = JsonConvert.SerializeObject(userGroup); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/DeleteUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -308,7 +308,7 @@ public async Task AddUsersToUserGroup(int userGroup var json = JsonConvert.SerializeObject(userUserGroups); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/AddUserUserGroups"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -359,7 +359,7 @@ public async Task AddUserGroupsToCatalogue(int cata var json = JsonConvert.SerializeObject(roleUserGroups); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/AddRoleUserGroups"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -403,7 +403,7 @@ public async Task DeleteUserUserGroup(int userUserG var json = JsonConvert.SerializeObject(userGroup); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/DeleteUserUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -447,7 +447,7 @@ public async Task DeleteRoleUserGroup(int roleUserG var json = JsonConvert.SerializeObject(roleUserGroupUpdate); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/DeleteRoleUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -502,7 +502,7 @@ public async Task> GetUserUserGroupPageAs var filter = JsonConvert.SerializeObject(pagingRequestModel.Filter); var presetFilter = JsonConvert.SerializeObject(pagingRequestModel.PresetFilter); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserUserGroupAdminFilteredPage" + $"/{pagingRequestModel.Page}" @@ -551,7 +551,7 @@ public async Task> GetRoleUserGroupPageAs var filter = JsonConvert.SerializeObject(pagingRequestModel.Filter); var presetFilter = JsonConvert.SerializeObject(pagingRequestModel.PresetFilter); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetRoleUserGroupAdminFilteredPage" + $"/{pagingRequestModel.Page}" @@ -598,7 +598,7 @@ private async Task> FetchRoleUserGroupDetailAsync() { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserGroupRoleDetail"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserService.cs index 7c64fd2c0..c84dbc2f2 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserService.cs @@ -31,10 +31,11 @@ public class UserService : BaseService, IUserService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The openApiHttpClient . /// The CacheService . /// The userApiHttpClient . - public UserService(ILearningHubHttpClient learningHubHttpClient, ICacheService cacheService, IUserApiHttpClient userApiHttpClient) - : base(learningHubHttpClient) + public UserService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ICacheService cacheService, IUserApiHttpClient userApiHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { this.cacheService = cacheService; this.userApiHttpClient = userApiHttpClient; @@ -166,7 +167,7 @@ public async Task GetEmailAddressRegistrationStatusAsyn var filter = JsonConvert.SerializeObject(pagingRequestModel.Filter); var presetFilter = JsonConvert.SerializeObject(pagingRequestModel.PresetFilter); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/GetLHUserAdminBasicFilteredPage" + $"/{pagingRequestModel.Page}" @@ -274,7 +275,7 @@ public async Task> GetUserCon var modelString = JsonConvert.SerializeObject(pagingRequestModel); var content = new StringContent(modelString, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceAdminSearchFilteredPage"; @@ -459,7 +460,7 @@ public async Task AddUserGroupsToUser(int userId, s var json = JsonConvert.SerializeObject(userUserGroups); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/AddUserUserGroups"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -565,7 +566,7 @@ public async Task> GetUserLearni var filter = JsonConvert.SerializeObject(pagingRequestModel.Filter); var presetFilter = JsonConvert.SerializeObject(pagingRequestModel.PresetFilter); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserLearningRecord/GetUserLearningRecords" + $"/{pagingRequestModel.Page}" diff --git a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json index b3a5c0f9c..95a04b8bc 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json +++ b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json @@ -17,6 +17,7 @@ "ELfhHubUrl": "", "LearningHubApiUrl": "", "UserApiUrl": "", + "OpenApiUrl": "", "LearningHubAdminUrl": "", "LogConfigDir": "D:\\learningHub\\NLog", "AuthenticationServiceUrl": "", diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index f0f2da3c1..12223f97c 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -31,6 +31,11 @@ public Settings() /// public string LearningHubApiUrl { get; set; } + /// + /// Gets or sets the OpenApiUrl. + /// + public string OpenApiUrl { get; set; } + /// /// Gets or sets the UserApiUrl. /// diff --git a/LearningHub.Nhs.WebUI/Helpers/IOpenApiFacade.cs b/LearningHub.Nhs.WebUI/Helpers/IOpenApiFacade.cs new file mode 100644 index 000000000..a603461c7 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/IOpenApiFacade.cs @@ -0,0 +1,59 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + + /// + /// Defines the . + /// + public interface IOpenApiFacade + { + /// + /// The GetAsync. + /// + /// The type. + /// The url. + /// The . + Task GetAsync(string url) + where T : class, new(); + + /// + /// The PostAsync. + /// + /// The type. + /// The url. + /// The body. + /// The . + Task PostAsync(string url, T body) + where T : class, new(); + + /// + /// The PostAsync. + /// + /// The type. + /// . + /// The url. + /// The body. + /// The . + Task PostAsync(string url, TBody body) + where T : class, new() + where TBody : class, new(); + + /// + /// The PutAsync. + /// + /// The url. + /// The . + Task PutAsync(string url); + + /// + /// The PutAsync. + /// + /// . + /// The url. + /// The body. + /// The . + Task PutAsync(string url, T body) + where T : class, new(); + } +} diff --git a/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs b/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs new file mode 100644 index 000000000..32c529a0d --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs @@ -0,0 +1,241 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using System; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.WebUI.Interfaces; + using Newtonsoft.Json; + + /// + /// Defines the . + /// + public class OpenApiFacade : IOpenApiFacade + { + /// + /// Defines the _client. + /// + private readonly IOpenApiHttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// The client. + public OpenApiFacade(IOpenApiHttpClient client) + { + this.client = client; + } + + /// + /// The GetAsync. + /// + /// . + /// The url. + /// The . + public async Task GetAsync(string url) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var vm = new T(); + + var response = await client.GetAsync(url).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + vm = JsonConvert.DeserializeObject(result); + + return vm; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PostAsync. + /// + /// . + /// The url. + /// The body. + /// The . + public async Task PostAsync(string url, T body) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PostAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + return; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PostAsync. + /// + /// The return type. + /// The type of body parameter. + /// The url. + /// The body. + /// The . + public async Task PostAsync(string url, TBody body) + where TBody : class, new() + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var vm = new T(); + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PostAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PostAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PutAsync. + /// + /// The url. + /// The . + public async Task PutAsync(string url) + { + var client = await this.client.GetClientAsync(); + + var response = await client.PutAsync(url, null).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PutAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + + /// + /// The PutAsync. + /// + /// . + /// The url. + /// The body. + /// The . + public async Task PutAsync(string url, T body) + where T : class, new() + { + var client = await this.client.GetClientAsync(); + + var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + var response = await client.PutAsync(url, content).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var apiResponse = JsonConvert.DeserializeObject(result); + if (apiResponse.Success) + { + return apiResponse; + } + else + { + string details = string.Empty; + if (apiResponse.ValidationResult != null) + { + if (apiResponse.ValidationResult.Details != null) + { + details = $"::ValidationResult: {string.Join(",", apiResponse.ValidationResult.Details)}"; + } + } + + throw new Exception($"PutAsync ApiResponse returned False: {details}"); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized + || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("Access Denied"); + } + else + { + throw new Exception($"Exception HttpStatusCode={response.StatusCode}"); + } + } + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs new file mode 100644 index 000000000..4c53a2c38 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.WebUI.Interfaces +{ + using System.Net.Http; + using System.Threading.Tasks; + + /// + /// The OpenApiHttpClient interface. + /// + public interface IOpenApiHttpClient + { + /// + /// The get client. + /// + /// The . + Task GetClientAsync(); + } +} diff --git a/LearningHub.Nhs.WebUI/Services/ActivityService.cs b/LearningHub.Nhs.WebUI/Services/ActivityService.cs index a494bea91..185dc3a2c 100644 --- a/LearningHub.Nhs.WebUI/Services/ActivityService.cs +++ b/LearningHub.Nhs.WebUI/Services/ActivityService.cs @@ -21,9 +21,10 @@ public class ActivityService : BaseService, IActivityService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// OpenApiHttpClient http client. /// Logger. - public ActivityService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public ActivityService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/BaseService.cs b/LearningHub.Nhs.WebUI/Services/BaseService.cs index 87d4cfc79..ddcbad86f 100644 --- a/LearningHub.Nhs.WebUI/Services/BaseService.cs +++ b/LearningHub.Nhs.WebUI/Services/BaseService.cs @@ -11,6 +11,7 @@ public abstract class BaseService { private readonly ILogger logger; private readonly ILearningHubHttpClient learningHubHttpClient; + private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. @@ -23,11 +24,29 @@ protected BaseService(ILearningHubHttpClient learningHubHttpClient, ILogger l this.logger = logger; } + /// + /// Initializes a new instance of the class. + /// + /// The learningHubHttpClient. + /// The openApiHttpClient. + /// The logger. + protected BaseService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + { + this.learningHubHttpClient = learningHubHttpClient; + this.openApiHttpClient = openApiHttpClient; + this.logger = logger; + } + /// /// Gets the LearningHubHttpClient. /// protected ILearningHubHttpClient LearningHubHttpClient => this.learningHubHttpClient; + /// + /// Gets the OpenApiHttpClient. + /// + protected IOpenApiHttpClient OpenApiHttpClient => this.openApiHttpClient; + /// /// Gets the Logger. /// diff --git a/LearningHub.Nhs.WebUI/Services/BoomarkService.cs b/LearningHub.Nhs.WebUI/Services/BoomarkService.cs index 5a26148cc..15d5509f9 100644 --- a/LearningHub.Nhs.WebUI/Services/BoomarkService.cs +++ b/LearningHub.Nhs.WebUI/Services/BoomarkService.cs @@ -19,16 +19,17 @@ public class BoomarkService : BaseService, IBookmarkService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The openApiHttpClient. /// The logger. - public BoomarkService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public BoomarkService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } /// public async Task Create(UserBookmarkViewModel bookmarkViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "bookmark/Create"; var content = new StringContent( JsonConvert.SerializeObject(bookmarkViewModel), @@ -56,7 +57,7 @@ public async Task Create(UserBookmarkViewModel bookmarkViewModel) /// public async Task Edit(UserBookmarkViewModel bookmarkViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "bookmark/Edit"; var content = new StringContent( JsonConvert.SerializeObject(bookmarkViewModel), @@ -84,7 +85,7 @@ public async Task Edit(UserBookmarkViewModel bookmarkViewModel) /// public async Task DeleteFolder(int bookmarkId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"bookmark/deletefolder/{bookmarkId}"; var response = await client.DeleteAsync(request).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized @@ -98,7 +99,7 @@ public async Task DeleteFolder(int bookmarkId) /// public async Task> GetAllByParent(int? parentId, bool? all = false) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"bookmark/GetAllByParent/{parentId}?all={all}"; var response = await client.GetAsync(request).ConfigureAwait(false); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized @@ -120,7 +121,7 @@ public async Task> GetAllByParent(int? parent /// public async Task Toggle(UserBookmarkViewModel bookmarkViewModel) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "bookmark/toggle"; var content = new StringContent( JsonConvert.SerializeObject(bookmarkViewModel), diff --git a/LearningHub.Nhs.WebUI/Services/CardService.cs b/LearningHub.Nhs.WebUI/Services/CardService.cs index d0795c0ad..12b312b07 100644 --- a/LearningHub.Nhs.WebUI/Services/CardService.cs +++ b/LearningHub.Nhs.WebUI/Services/CardService.cs @@ -19,9 +19,10 @@ public class CardService : BaseService, ICardService /// Initializes a new instance of the class. /// /// The Learning Hub Http Client. + /// The Open Api Http Client. /// The logger. - public CardService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public CardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs index a8a6d3055..d36057194 100644 --- a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs @@ -25,10 +25,11 @@ public class CatalogueService : BaseService, ICatalogueService /// Initializes a new instance of the class. /// /// The learning hub http client. + /// The Open Api Http Client. /// The logger. /// The cacheService. - public CatalogueService(ILearningHubHttpClient learningHubHttpClient, ILogger logger, ICacheService cacheService) - : base(learningHubHttpClient, logger) + public CatalogueService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, ICacheService cacheService) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.cacheService = cacheService; } @@ -70,7 +71,7 @@ public async Task GetCatalogueAsync(string reference) { CatalogueViewModel viewmodel = new CatalogueViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"catalogue/catalogue/{reference}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/ContentService.cs b/LearningHub.Nhs.WebUI/Services/ContentService.cs index 92fe7a7dd..b6d1f9296 100644 --- a/LearningHub.Nhs.WebUI/Services/ContentService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContentService.cs @@ -18,10 +18,11 @@ public class ContentService : BaseService, IContentService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. /// azureMediaService. - public ContentService(ILearningHubHttpClient learningHubHttpClient, ILogger logger, IAzureMediaService azureMediaService) - : base(learningHubHttpClient, logger) + public ContentService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IAzureMediaService azureMediaService) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.azureMediaService = azureMediaService; } @@ -36,7 +37,7 @@ public async Task GetPageByIdAsync(int id, bool preview) { PageViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync($"content/page/{id}?publishedOnly={!preview}&preview={preview}").ConfigureAwait(false); @@ -63,7 +64,7 @@ public async Task GetPageSectionDetailVideoAssetById { PageSectionDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync($"content/page-section-detail-video/{id}").ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/ContributeService.cs b/LearningHub.Nhs.WebUI/Services/ContributeService.cs index a61487e4e..1638e1ef1 100644 --- a/LearningHub.Nhs.WebUI/Services/ContributeService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContributeService.cs @@ -39,9 +39,10 @@ public class ContributeService : BaseService, IContributeServ /// Azure media service. /// MKIO media service. /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public ContributeService(IFileService fileService, IResourceService resourceService, IAzureMediaService azureMediaService, ILearningHubHttpClient learningHubHttpClient, ILogger logger, IAzureMediaService mediaService) - : base(learningHubHttpClient, logger) + public ContributeService(IFileService fileService, IResourceService resourceService, IAzureMediaService azureMediaService, ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IAzureMediaService mediaService) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.fileService = fileService; this.resourceService = resourceService; @@ -62,7 +63,7 @@ public async Task CreateNewResourceVersionAsync(int var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/CreateNewResourceVersion"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -96,7 +97,7 @@ public async Task CreateResourceAuthorAsync(ResourceAuthorViewModel resourc var json = JsonConvert.SerializeObject(resourceAuthorViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/AddResourceVersionAuthor"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -130,7 +131,7 @@ public async Task CreateResourceKeywordAsync(ResourceKeywordViewModel resou var json = JsonConvert.SerializeObject(resourceKeywordViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/AddResourceVersionKeyword"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -163,7 +164,7 @@ public async Task DeleteArticleFileAsync(FileDeleteRequestModel model) var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteArticleFile"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -193,7 +194,7 @@ public async Task DeleteArticleFileAsync(FileDeleteRequestModel model) /// The . public async Task DeleteFileChunkDetailAsync(int fileChunkDetailId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteFileChunkDetail/{fileChunkDetailId}"; var response = await client.DeleteAsync(request).ConfigureAwait(false); @@ -224,7 +225,7 @@ public async Task DeleteResourceAttributeFileAsync(FileDeleteRequestModel var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteResourceAttributeFile"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -257,7 +258,7 @@ public async Task DeleteResourceAuthorAsync(AuthorDeleteRequestModel model var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteResourceVersionAuthor"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -290,7 +291,7 @@ public async Task DeleteResourceKeywordAsync(KeywordDeleteRequestModel mod var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteResourceVersionKeyword"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -320,7 +321,7 @@ public async Task DeleteResourceKeywordAsync(KeywordDeleteRequestModel mod /// The . public async Task DeleteResourceVersionAsync(int resourceVersionId) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteResourceVersion/{resourceVersionId}"; var response = await client.DeleteAsync(request).ConfigureAwait(false); @@ -1335,7 +1336,7 @@ private async Task CreateResourceAsync(ResourceDetailViewModel resourceDeta var json = JsonConvert.SerializeObject(resourceDetailViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/CreateResource"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1368,7 +1369,7 @@ private async Task UpdateResourceVersionAsync(ResourceDetailViewModel resourceDe var json = JsonConvert.SerializeObject(resourceDetailViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateResourceVersion"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/CountryService.cs b/LearningHub.Nhs.WebUI/Services/CountryService.cs index 2c0c7e120..e1afd4b35 100644 --- a/LearningHub.Nhs.WebUI/Services/CountryService.cs +++ b/LearningHub.Nhs.WebUI/Services/CountryService.cs @@ -20,13 +20,15 @@ public class CountryService : BaseService, ICountryService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public CountryService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 55f5153bf..56d132e3b 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -26,10 +26,11 @@ public class DashboardService : BaseService, IDashboardService /// Initializes a new instance of the class. /// /// learningHubHttpClient. + /// The Open Api Http Client. /// logger. /// MoodleHttpClient. - public DashboardService(ILearningHubHttpClient learningHubHttpClient, ILogger logger, IMoodleHttpClient moodleHttpClient) - : base(learningHubHttpClient, logger) + public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IMoodleHttpClient moodleHttpClient) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.moodleHttpClient = moodleHttpClient; } @@ -44,7 +45,7 @@ public async Task GetMyAccessLearningsAsyn { DashboardMyLearningResponseViewModel viewmodel = new DashboardMyLearningResponseViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"dashboard/myaccesslearning/{dashboardType}/{pageNumber}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -73,7 +74,7 @@ public async Task GetCataloguesAsync(string { DashboardCatalogueResponseViewModel viewmodel = new DashboardCatalogueResponseViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"dashboard/catalogues/{dashboardType}/{pageNumber}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -102,7 +103,7 @@ public async Task GetResourcesAsync(string d { DashboardResourceResponseViewModel viewmodel = new DashboardResourceResponseViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"dashboard/resources/{dashboardType}/{pageNumber}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs b/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs index da62c5cbd..e9bf607ce 100644 --- a/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs +++ b/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs @@ -14,9 +14,10 @@ public class DetectJsLogService : BaseService, IDetectJsLogS /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public DetectJsLogService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public DetectJsLogService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/GradeService.cs b/LearningHub.Nhs.WebUI/Services/GradeService.cs index fb0a42074..37df9b048 100644 --- a/LearningHub.Nhs.WebUI/Services/GradeService.cs +++ b/LearningHub.Nhs.WebUI/Services/GradeService.cs @@ -19,13 +19,15 @@ public class GradeService : BaseService, IGradeService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public GradeService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/HierarchyService.cs b/LearningHub.Nhs.WebUI/Services/HierarchyService.cs index 4bd8f7734..6bed5fd27 100644 --- a/LearningHub.Nhs.WebUI/Services/HierarchyService.cs +++ b/LearningHub.Nhs.WebUI/Services/HierarchyService.cs @@ -16,21 +16,23 @@ public class HierarchyService : BaseService, IHierarchyService /// /// Defines the _facade. /// - private readonly ILearningHubApiFacade facade; + private readonly IOpenApiFacade facade; /// /// Initializes a new instance of the class. /// /// The learning hub http client. - /// The learningHubApiFacade. + /// The Open Api Http Client. + /// The openApiFacade. /// The logger. public HierarchyService( ILearningHubHttpClient learningHubHttpClient, - ILearningHubApiFacade learningHubApiFacade, + IOpenApiHttpClient openApiHttpClient, + IOpenApiFacade openApiFacade, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { - this.facade = learningHubApiFacade; + this.facade = openApiFacade; } /// diff --git a/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs b/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs index d578d9523..bb19a0f38 100644 --- a/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs +++ b/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs @@ -16,9 +16,10 @@ public class InternalSystemService : BaseService, IIntern /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public InternalSystemService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public InternalSystemService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/JobRoleService.cs b/LearningHub.Nhs.WebUI/Services/JobRoleService.cs index bbf9f4402..766d05e60 100644 --- a/LearningHub.Nhs.WebUI/Services/JobRoleService.cs +++ b/LearningHub.Nhs.WebUI/Services/JobRoleService.cs @@ -22,13 +22,15 @@ public class JobRoleService : BaseService, IJobRoleService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public JobRoleService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/LocationService.cs b/LearningHub.Nhs.WebUI/Services/LocationService.cs index 2ba678ab0..04423a0b6 100644 --- a/LearningHub.Nhs.WebUI/Services/LocationService.cs +++ b/LearningHub.Nhs.WebUI/Services/LocationService.cs @@ -19,13 +19,15 @@ public class LocationService : BaseService, ILocationService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public LocationService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs b/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs index 306d69304..06fd7e428 100644 --- a/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs +++ b/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs @@ -24,13 +24,15 @@ public class LoginWizardService : BaseService, ILoginWizardS /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public LoginWizardService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs index c6f9f2ffb..1e1662201 100644 --- a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs @@ -20,9 +20,10 @@ public class MyLearningService : BaseService, IMyLearningServ /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The Open Api Http Client. /// The logger. - public MyLearningService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public MyLearningService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } @@ -38,7 +39,7 @@ public async Task GetActivityDetailed(MyLearningReq var json = JsonConvert.SerializeObject(requestModel); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"MyLearning/GetActivityDetailed"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -67,7 +68,7 @@ public async Task GetActivityDetailed(MyLearningReq public async Task> GetPlayedSegments(int resourceId, int majorVersion) { List viewModel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"MyLearning/GetPlayedSegments/{resourceId}/{majorVersion}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -98,7 +99,7 @@ public async Task> GetPlayedSegments(int resourceId public async Task> GetResourceCertificateDetails(int resourceReferenceId, int? majorVersion = 0, int? minorVersion = 0, int? userId = 0) { Tuple viewModel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"MyLearning/GetResourceCertificateDetails/{resourceReferenceId}/{majorVersion}/{minorVersion}/{userId}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/NotificationService.cs b/LearningHub.Nhs.WebUI/Services/NotificationService.cs index d41ef30d6..e836edcd9 100644 --- a/LearningHub.Nhs.WebUI/Services/NotificationService.cs +++ b/LearningHub.Nhs.WebUI/Services/NotificationService.cs @@ -22,9 +22,10 @@ public class NotificationService : BaseService, INotificati /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public NotificationService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public NotificationService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } @@ -95,7 +96,7 @@ public async Task> GetPagedAsync(Pagin { PagedResultSet viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(pagingRequestModel), Encoding.UTF8, "application/json"); @@ -124,7 +125,7 @@ public async Task GetUserNotificationIdAsync(int id) { UserNotification viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserNotification/GetById/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -153,7 +154,7 @@ public async Task GetUserNotificationDetailsAsync(int id) { UserNotification viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserNotification/GetByIdAndUserId/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -182,7 +183,7 @@ public async Task GetUserUnreadNotificationCountAsync(int userid) { int count = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserNotification/GetUserUnreadNotificationCount/{userid}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -212,7 +213,7 @@ private async Task PutAsync(UserNotification notification) var json = JsonConvert.SerializeObject(notification); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserNotification/PutAsync"; var response = await client.PutAsync(request, stringContent).ConfigureAwait(false); @@ -245,7 +246,7 @@ private async Task PostAsync(UserNotification notification) var json = JsonConvert.SerializeObject(notification); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserNotification/PostAsync"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs b/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs new file mode 100644 index 000000000..54c3b7bb2 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.WebUI.Services +{ + using System.Net.Http; + using LearningHub.Nhs.Caching; + using LearningHub.Nhs.WebUI.Configuration; + using LearningHub.Nhs.WebUI.Interfaces; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + + /// + /// The open api http client. + /// + public class OpenApiHttpClient : BaseHttpClient, IOpenApiHttpClient + { + /// + /// Initializes a new instance of the class. + /// + /// The http context accessor. + /// The web settings. + /// The auth config. + /// The http client. + /// The logger. + /// The cache service. + public OpenApiHttpClient( + IHttpContextAccessor httpContextAccessor, + IOptions webSettings, + LearningHubAuthServiceConfig authConfig, + HttpClient client, + ILogger logger, + ICacheService cacheService) + : base(httpContextAccessor, webSettings.Value, authConfig, client, logger, cacheService) + { + } + + /// + /// Gets the open api url. + /// + public override string ApiUrl => this.WebSettings.OpenApiUrl; + } +} diff --git a/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs b/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs index 7a7a33616..5ef45d685 100644 --- a/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs +++ b/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs @@ -34,12 +34,14 @@ public class PartialFileUploadService : BaseService, I /// /// The Settings. /// The learning hub http client. + /// The Open Api Http Client. /// The logger. public PartialFileUploadService( IOptions settings, ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.settings = settings.Value; } diff --git a/LearningHub.Nhs.WebUI/Services/ProviderService.cs b/LearningHub.Nhs.WebUI/Services/ProviderService.cs index 44a91495b..daf2c4273 100644 --- a/LearningHub.Nhs.WebUI/Services/ProviderService.cs +++ b/LearningHub.Nhs.WebUI/Services/ProviderService.cs @@ -22,9 +22,10 @@ public class ProviderService : BaseService, IProviderService /// /// The cache service. /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public ProviderService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public ProviderService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.cacheService = cacheService; } diff --git a/LearningHub.Nhs.WebUI/Services/RatingService.cs b/LearningHub.Nhs.WebUI/Services/RatingService.cs index f08361096..fa72f4264 100644 --- a/LearningHub.Nhs.WebUI/Services/RatingService.cs +++ b/LearningHub.Nhs.WebUI/Services/RatingService.cs @@ -19,9 +19,10 @@ public class RatingService : BaseService, IRatingService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public RatingService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public RatingService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/RegionService.cs b/LearningHub.Nhs.WebUI/Services/RegionService.cs index 6a48fcf5d..f05fc22eb 100644 --- a/LearningHub.Nhs.WebUI/Services/RegionService.cs +++ b/LearningHub.Nhs.WebUI/Services/RegionService.cs @@ -19,10 +19,11 @@ public class RegionService : BaseService, IRegionService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Logger. /// User Api http client. - public RegionService(ILearningHubHttpClient learningHubHttpClient, ILogger logger, IUserApiHttpClient userApiHttpClient) - : base(learningHubHttpClient, logger) + public RegionService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IUserApiHttpClient userApiHttpClient) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/ResourceService.cs b/LearningHub.Nhs.WebUI/Services/ResourceService.cs index 02a233ea0..ec8eaee30 100644 --- a/LearningHub.Nhs.WebUI/Services/ResourceService.cs +++ b/LearningHub.Nhs.WebUI/Services/ResourceService.cs @@ -34,11 +34,12 @@ public class ResourceService : BaseService, IResourceService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Azure media services. /// Logger. /// Settings. - public ResourceService(ILearningHubHttpClient learningHubHttpClient, IAzureMediaService azureMediaService, ILogger logger, IOptions settings) - : base(learningHubHttpClient, logger) + public ResourceService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IAzureMediaService azureMediaService, ILogger logger, IOptions settings) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.settings = settings.Value; this.azureMediaService = azureMediaService; @@ -55,7 +56,7 @@ public async Task AcceptSensitiveContentAsync(int r var stringContent = new StringContent(resourceVersionId.ToString(), Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/AcceptSensitiveContent"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -87,7 +88,7 @@ public async Task GetArticleDetailsByIdAsync(int resourceVersi { ArticleViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetArticleDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -116,7 +117,7 @@ public async Task GetAudioDetailsByIdAsync(int resourceVersionId { AudioViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetAudioDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -145,7 +146,7 @@ public async Task GetByIdAsync(int id) { ResourceHeaderViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceHeaderViewModelAsync/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -173,7 +174,7 @@ public async Task> GetFileTypeAsync() { List fileTypeList = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetFileTypes"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -202,7 +203,7 @@ public async Task GetGenericFileDetailsByIdAsync(int resou { GenericFileViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetGenericFileDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -231,7 +232,7 @@ public async Task GetHtmlDetailsByIdAsync(int resourceVer { HtmlResourceViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetHtmlDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -260,7 +261,7 @@ public async Task GetScormDetailsByIdAsync(int resourceVersionId { ScormViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetScormDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -288,7 +289,7 @@ public async Task GetExternalContentDetailsAsyn { ExternalContentDetailsViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetExternalContentDetailsById/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -317,7 +318,7 @@ public async Task RecordExternalReferenceUserAgreementAsync(ExternalRefere var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/RecordExternalReferenceUserAgreement"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -349,7 +350,7 @@ public async Task GetImageDetailsByIdAsync(int resourceVersionId { ImageViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetImageDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -378,7 +379,7 @@ public async Task GetInformationByIdAsync(int id) { ResourceInformationViewModel resourceInfo = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceInformationViewModelAsync/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -407,7 +408,7 @@ public async Task GetItemByIdAsync(int id) { ResourceItemViewModel resourceItem = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceItemViewModelAsync/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -453,7 +454,7 @@ public async Task> GetLicencesAsync() { List licences = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceLicences"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -482,7 +483,7 @@ public async Task GetLocationsByIdAsync(int id) { CatalogueLocationsViewModel resourceLocations = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetCatalogueLocations/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -511,7 +512,7 @@ public async Task GetResourceVersionAsync(int resourceV { ResourceDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersion/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -540,7 +541,7 @@ public async Task GetResourceVersionViewModelAsync(int { ResourceVersionViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionViewModel/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -569,7 +570,7 @@ public async Task GetResourceVersionExtendedAs { ResourceVersionExtendedViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionExtendedViewModel/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -609,7 +610,7 @@ public async Task GetVideoDetailsByIdAsync(int resourceVersionId { VideoViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetVideoDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -648,7 +649,7 @@ public async Task GetWeblinkDetailsByIdAsync(int resourceVersi { WebLinkViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetWeblinkDetails/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -677,7 +678,7 @@ public async Task GetCaseDetailsByIdAsync(int resourceVersionId) { CaseViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetCaseDetails/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -706,7 +707,7 @@ public async Task GetAssessmentDetailsByIdAsync(int resourc { AssessmentViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetAssessmentDetails/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -735,7 +736,7 @@ public async Task GetAssessmentContent(int resourceVersionI { AssessmentViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetAssessmentContent/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -764,7 +765,7 @@ public async Task GetAssessmentProgressByResourceVe { AssessmentProgressViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetAssessmentProgress/resource/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -793,7 +794,7 @@ public async Task GetAssessmentProgressByActivity(i { AssessmentProgressViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetAssessmentProgress/activity/{assessmentResourceActivityId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -822,7 +823,7 @@ public async Task> GetFileStatusDetailsAsync(int[] fileIds) { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); string queryString = string.Join("&", fileIds.Select(fileId => $"fileIds={fileId}")); var request = $"Resource/GetFileStatusDetails?{queryString}"; @@ -855,7 +856,7 @@ public async Task UnpublishResourceVersionAsync(int var json = JsonConvert.SerializeObject(rv); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UnpublishResourceVersion"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -884,7 +885,7 @@ public async Task UnpublishResourceVersionAsync(int /// The . public async Task UserHasPublishedResourcesAsync() { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/HasPublishedResources"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -916,7 +917,7 @@ public async Task DuplicateResourceAsync(DuplicateR var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DuplicateResource"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -963,7 +964,7 @@ public async Task DuplicateBlocksAsync(DuplicateBlo var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DuplicateBlocks"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1007,7 +1008,7 @@ public async Task> GetMyContri { var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.PostAsync("Resource/GetMyContributions", stringContent).ConfigureAwait(false); @@ -1030,7 +1031,7 @@ public async Task> GetMyContri /// The . public async Task> GetAllPublishedResourceAsync() { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync("Resource/GetAllPublishedResource").ConfigureAwait(false); @@ -1056,7 +1057,7 @@ public async Task GetResourceVersionVa { ResourceVersionValidationResultViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionValidationResult/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1085,7 +1086,7 @@ public async Task GetResourceVersionExtendedVi { ResourceVersionExtendedViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetResourceVersionExtendedViewModel/{resourceVersionId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1116,7 +1117,7 @@ public async Task RevertToDraft(int resourceVersion var json = JsonConvert.SerializeObject(new { resourceVersionId }); var stringContent = new StringContent(resourceVersionId.ToString(), Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/RevertToDraft"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1160,7 +1161,7 @@ public async Task CreateResourceVersionProviderAsyn var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/AddResourceProvider"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1206,7 +1207,7 @@ public async Task CreateResourceVersionValidationResultAsync(ResourceVersionVali var json = JsonConvert.SerializeObject(validationResultViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/CreateResourceVersionValidationResult"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1237,7 +1238,7 @@ public async Task DeleteResourceVersionProviderAsyn var json = JsonConvert.SerializeObject(model); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteResourceProvider"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1278,7 +1279,7 @@ public async Task DeleteResourceVersionProviderAsyn public async Task DeleteAllResourceVersionProviderAsync(int resourceVersionId) { var content = new StringContent(JsonConvert.SerializeObject(new { })); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/DeleteAllResourceProvider/{resourceVersionId}"; var response = await client.PostAsync(request, content).ConfigureAwait(false); @@ -1320,7 +1321,7 @@ public async Task> GetObsoleteResourceFile(int resourceVersionId, b { List filePaths = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetObsoleteResourceFile/{resourceVersionId}/{deletedResource}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/RoadMapService.cs b/LearningHub.Nhs.WebUI/Services/RoadMapService.cs index 210a06096..e63f9b9c1 100644 --- a/LearningHub.Nhs.WebUI/Services/RoadMapService.cs +++ b/LearningHub.Nhs.WebUI/Services/RoadMapService.cs @@ -16,9 +16,10 @@ public class RoadMapService : BaseService, IRoadMapService /// Initializes a new instance of the class. /// /// Learing hub http client. + /// The Open Api Http Client. /// Logger. - public RoadMapService(ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public RoadMapService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { } diff --git a/LearningHub.Nhs.WebUI/Services/RoleService.cs b/LearningHub.Nhs.WebUI/Services/RoleService.cs index cfc3c8ff8..c4f37af65 100644 --- a/LearningHub.Nhs.WebUI/Services/RoleService.cs +++ b/LearningHub.Nhs.WebUI/Services/RoleService.cs @@ -21,9 +21,10 @@ public class RoleService : BaseService, IRoleService /// /// The cache service. /// Learning hub http client. + /// The Open Api Http Client. /// Logger. - public RoleService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + public RoleService(ICacheService cacheService, ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.cacheService = cacheService; } diff --git a/LearningHub.Nhs.WebUI/Services/SearchService.cs b/LearningHub.Nhs.WebUI/Services/SearchService.cs index 41ff87edb..6593aa4c9 100644 --- a/LearningHub.Nhs.WebUI/Services/SearchService.cs +++ b/LearningHub.Nhs.WebUI/Services/SearchService.cs @@ -34,11 +34,12 @@ public class SearchService : BaseService, ISearchService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// Provider service. /// Logger. /// Settings. - public SearchService(ILearningHubHttpClient learningHubHttpClient, IProviderService providerService, ILogger logger, IOptions settings) - : base(learningHubHttpClient, logger) + public SearchService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IProviderService providerService, ILogger logger, IOptions settings) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.settings = settings.Value; this.providerService = providerService; @@ -345,7 +346,7 @@ public async Task CreateResourceSearchActionAsync(SearchActionResourceModel try { int createId = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(searchActionResourceModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -388,7 +389,7 @@ public async Task CreateCatalogueSearchActionAsync(SearchActionCatalogueMod try { int createId = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(searchActionCatalogueModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -427,7 +428,7 @@ public async Task CreateSearchTermEventAsync(SearchRequestModel searchReque try { int createId = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(searchRequestModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -467,7 +468,7 @@ public async Task GetSearchResultAsync(SearchRequestModel searc try { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); searchRequestModel.SearchText = this.DecodeProblemCharacters(searchRequestModel.SearchText); @@ -510,8 +511,8 @@ public async Task SubmitFeedbackAsync(SearchFeedBackModel model) { int createId = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); - var request = this.settings.LearningHubApiUrl + "Search/SubmitFeedback"; + var client = await this.OpenApiHttpClient.GetClientAsync(); + var request = this.settings.OpenApiUrl + "Search/SubmitFeedback"; var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); @@ -547,7 +548,7 @@ public async Task GetCatalogueSearchResultAsync(Catalo try { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); catalogueSearchRequestModel.SearchText = this.DecodeProblemCharacters(catalogueSearchRequestModel.SearchText); @@ -593,7 +594,7 @@ public async Task CreateCatalogueSearchTermEventAsync(CatalogueSearchReques try { int createId = 0; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(catalogueSearchRequestModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -637,7 +638,7 @@ public async Task GetAllCatalogueSearchResultAsync( try { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); catalogueSearchRequestModel.SearchText = this.DecodeProblemCharacters(catalogueSearchRequestModel.SearchText); @@ -680,7 +681,7 @@ public async Task GetAllCatalogueSearchResultAsync( /// The auto suggestion list. public async Task GetAutoSuggestionList(string term) { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Search/GetAutoSuggestionResult/{term}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -707,7 +708,7 @@ public async Task SendAutoSuggestionClickActionAsync(AutoSuggestionClickPayloadM { try { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(clickPayloadModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); diff --git a/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs b/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs index adc3f41bb..4df5f36e7 100644 --- a/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs +++ b/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs @@ -19,13 +19,15 @@ public class SpecialtyService : BaseService, ISpecialtyService /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// . public SpecialtyService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs b/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs index db00925e6..6c7220499 100644 --- a/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs +++ b/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs @@ -22,13 +22,15 @@ public class TermsAndConditionsService : BaseService, /// Initializes a new instance of the class. /// /// Learning hub http client. + /// The Open Api Http Client. /// User api http client. /// Logger. public TermsAndConditionsService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; } diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 6790a3143..cee569d72 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -25,17 +25,19 @@ public class UserGroupService : BaseService, IUserGroupService /// Initializes a new instance of the class. /// /// The learning hub http client. + /// The Open Api Http Client. /// The logger. /// The http context accessor. /// The cacheService. /// roleService. public UserGroupService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, ILogger logger, IHttpContextAccessor contextAccessor, ICacheService cacheService, IRoleService roleService) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.contextAccessor = contextAccessor; this.cacheService = cacheService; diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 942360eae..98509080b 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -54,6 +54,7 @@ public class UserService : BaseService, IUserService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The Open Api Http Client. /// The userApiHttpClient. /// The logger. /// The settings. @@ -66,6 +67,7 @@ public class UserService : BaseService, IUserService /// The login wizard service service. public UserService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IUserApiHttpClient userApiHttpClient, ILogger logger, IOptions settings, @@ -76,7 +78,7 @@ public UserService( ILocationService locationService, IGradeService gradeService, ILoginWizardService loginWizardService) - : base(learningHubHttpClient, logger) + : base(learningHubHttpClient, openApiHttpClient, logger) { this.userApiHttpClient = userApiHttpClient; this.settings = settings.Value; diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index fdab0fc8a..0a0c04c49 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -39,6 +39,14 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); + services.AddHttpClient() + .ConfigurePrimaryHttpMessageHandler( + () => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }); + services.AddHttpClient() .ConfigurePrimaryHttpMessageHandler( () => new HttpClientHandler @@ -65,6 +73,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon else { services.AddHttpClient(); + services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); @@ -103,6 +112,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index edb50316b..3b4c5844e 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -18,106 +18,107 @@ } }, "AllowedHosts": "*", - "Settings": { - "BuildNumber": "NotSet", - "KnownProxies": "", - "UserApiUrl": "", - "LearningHubApiUrl": "", - "LearningHubWebUiUrl": "", - "LearningHubAdminUrl": "", - "ELfhHubUrl": "", - "LHClientIdentityKey": "", - "LearningHubTenantId": "10", - "LogConfigDir": "", - "TenantCode": "LearningHub", - "TenantName": "Learning Hub", - "GoogleAnalyticsId": "", - "GoogleTagManagerEnabled": true, - "GoogleTagManagerContainerId": "", - "KeepUserSessionAliveIntervalMins": 15, - "SecurityQuestionsToAsk": 2, - "Restricted": false, - "PasswordRequestLimitingPeriod": 1, // minutes - "PasswordRequestLimit": 2, - "ConcurrentId": 0, - "AzureBlobSettings": { - "ConnectionString": "", - "UploadContainer": "" - }, - "AzureFileStorageConnectionString": "", - "AzureSourceArchiveStorageConnectionString": "", - "AzureContentArchiveStorageConnectionString": "", - "AzureFileStorageResourceShareName": "resourcesdev", - "AzureMediaIssuer": "LearningHub", - "AzureMediaAudience": "LearningHubUsers", - "AzureMediaAadClientId": "", - "AzureMediaAadSecret": "", - "AzureMediaAadTenantId": "e-lfh.org.uk", - "AzureMediaAccountName": "", - "AzureMediaArmEndpoint": "https://management.azure.com/", - "AzureMediaResourceGroup": "", - "AzureMediaSubscriptionId": "", - "AzureMediaJWTPrimaryKeySecret": "", - "AzureMediaJWTTokenExpiryMinutes": 20, - "ResourceLicenseUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986606-which-licence-should-i-select-when-contributing-a-resource-to-the-learning-hub-", - "SupportUrlExcludedFiles": "https://localhost/resource/excludedfiles", - "MediaActivityPlayingEventIntervalSeconds": 5, - "TwitterCredentials": { - "ConsumerKey": "", - "ConsumerSecret": "", - "AccessToken": "", - "AccessTokenSecret": "", - "ScreenName": "" - }, - "SupportUrls": { - "SupportSite": "https://support.learninghub.nhs.uk/", - "BookmarksHelpUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000987925-how-to-create-bookmarks", - "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", - "SupportFeedbackForm": "https://forms.office.com/e/nKcK8AdHRX", - "ExcludedFiles": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986609-which-types-of-resources-can-i-share-on-the-learning-hub-", - "WhoCanAccessTheLearningHub": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986599-who-can-access-the-learning-hub-", - "ContributeUR": "https://forms.office.com/e/nKcK8AdHRX", - "PrivacyNotice": "https://www.hee.nhs.uk/about/privacy-notice", - "UnableToViewELearningResources": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986607-why-am-i-unable-to-view-e-learning-resources-", - "ContributingAResource": "https://support.learninghub.nhs.uk/support/tickets/new", - "UserAccountUpgrade": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986599-who-can-access-the-learning-hub-", - "ReportAResource": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986627-how-do-i-offer-feedback-or-report-a-resource-on-the-learning-hub-", - "MyLearningHelpUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986595-my-learning-an-introduction", - "ValidationErrorsHelpUrl": "https://support.learninghub.nhs.uk/", - "ResourceCertificateUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80001091159-how-to-enable-certificates-on-your-learning-resource" - }, - "SocialMediaSharingUrls": { - "Facebook": "https://www.facebook.com/sharer.php?u=", - "Twitter": "https://twitter.com/intent/tweet?url=", - "LinkedIn": "https://www.linkedin.com/sharing/share-offsite/?url=" - }, - "FileUploadSettings": { - "FileUploadSizeLimit": 2147483648, // 2GB - "FileUploadSizeLimitText": "2GB", - "AllowedThreads": 3, - "ChunkSize": 10485760, // 10MB - "TimeoutSec": 20 - }, - "FindwiseSettings": { - "ResourceSearchPageSize": 10, - "CatalogueSearchPageSize": 3, - "AllCatalogueSearchPageSize": 10 - }, - "MediaKindSettings": { - "StorageAccountName": "", - "Token": "", - "SubscriptionName": "", - "Issuer": "LearningHub", - "Audience": "LearningHubUsers", - "ContentKeyPolicyName": "LHSharedContentKeyPolicy", - "JWTPrimaryKeySecret": "", - "MKPlayerLicence": "", - "MediaKindStorageConnectionString": "" - }, - "EnableTempDebugging": "false", - "LimitScormToAdmin": "false" - + "Settings": { + "BuildNumber": "NotSet", + "KnownProxies": "", + "UserApiUrl": "", + "OpenApiUrl": "", + "LearningHubApiUrl": "", + "LearningHubWebUiUrl": "", + "LearningHubAdminUrl": "", + "ELfhHubUrl": "", + "LHClientIdentityKey": "", + "LearningHubTenantId": "10", + "LogConfigDir": "", + "TenantCode": "LearningHub", + "TenantName": "Learning Hub", + "GoogleAnalyticsId": "", + "GoogleTagManagerEnabled": true, + "GoogleTagManagerContainerId": "", + "KeepUserSessionAliveIntervalMins": 15, + "SecurityQuestionsToAsk": 2, + "Restricted": false, + "PasswordRequestLimitingPeriod": 1, // minutes + "PasswordRequestLimit": 2, + "ConcurrentId": 0, + "AzureBlobSettings": { + "ConnectionString": "", + "UploadContainer": "" + }, + "AzureFileStorageConnectionString": "", + "AzureSourceArchiveStorageConnectionString": "", + "AzureContentArchiveStorageConnectionString": "", + "AzureFileStorageResourceShareName": "resourcesdev", + "AzureMediaIssuer": "LearningHub", + "AzureMediaAudience": "LearningHubUsers", + "AzureMediaAadClientId": "", + "AzureMediaAadSecret": "", + "AzureMediaAadTenantId": "e-lfh.org.uk", + "AzureMediaAccountName": "", + "AzureMediaArmEndpoint": "https://management.azure.com/", + "AzureMediaResourceGroup": "", + "AzureMediaSubscriptionId": "", + "AzureMediaJWTPrimaryKeySecret": "", + "AzureMediaJWTTokenExpiryMinutes": 20, + "ResourceLicenseUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986606-which-licence-should-i-select-when-contributing-a-resource-to-the-learning-hub-", + "SupportUrlExcludedFiles": "https://localhost/resource/excludedfiles", + "MediaActivityPlayingEventIntervalSeconds": 5, + "TwitterCredentials": { + "ConsumerKey": "", + "ConsumerSecret": "", + "AccessToken": "", + "AccessTokenSecret": "", + "ScreenName": "" + }, + "SupportUrls": { + "SupportSite": "https://support.learninghub.nhs.uk/", + "BookmarksHelpUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000987925-how-to-create-bookmarks", + "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", + "SupportFeedbackForm": "https://forms.office.com/e/nKcK8AdHRX", + "ExcludedFiles": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986609-which-types-of-resources-can-i-share-on-the-learning-hub-", + "WhoCanAccessTheLearningHub": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986599-who-can-access-the-learning-hub-", + "ContributeUR": "https://forms.office.com/e/nKcK8AdHRX", + "PrivacyNotice": "https://www.hee.nhs.uk/about/privacy-notice", + "UnableToViewELearningResources": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986607-why-am-i-unable-to-view-e-learning-resources-", + "ContributingAResource": "https://support.learninghub.nhs.uk/support/tickets/new", + "UserAccountUpgrade": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986599-who-can-access-the-learning-hub-", + "ReportAResource": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986627-how-do-i-offer-feedback-or-report-a-resource-on-the-learning-hub-", + "MyLearningHelpUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80000986595-my-learning-an-introduction", + "ValidationErrorsHelpUrl": "https://support.learninghub.nhs.uk/", + "ResourceCertificateUrl": "https://support.learninghub.nhs.uk/support/solutions/articles/80001091159-how-to-enable-certificates-on-your-learning-resource" + }, + "SocialMediaSharingUrls": { + "Facebook": "https://www.facebook.com/sharer.php?u=", + "Twitter": "https://twitter.com/intent/tweet?url=", + "LinkedIn": "https://www.linkedin.com/sharing/share-offsite/?url=" }, + "FileUploadSettings": { + "FileUploadSizeLimit": 2147483648, // 2GB + "FileUploadSizeLimitText": "2GB", + "AllowedThreads": 3, + "ChunkSize": 10485760, // 10MB + "TimeoutSec": 20 + }, + "FindwiseSettings": { + "ResourceSearchPageSize": 10, + "CatalogueSearchPageSize": 3, + "AllCatalogueSearchPageSize": 10 + }, + "MediaKindSettings": { + "StorageAccountName": "", + "Token": "", + "SubscriptionName": "", + "Issuer": "LearningHub", + "Audience": "LearningHubUsers", + "ContentKeyPolicyName": "LHSharedContentKeyPolicy", + "JWTPrimaryKeySecret": "", + "MKPlayerLicence": "", + "MediaKindStorageConnectionString": "" + }, + "EnableTempDebugging": "false", + "LimitScormToAdmin": "false" + + }, "LearningHubAuthServiceConfig": { "Authority": "", "ClientId": "", diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs index 009509127..3a62d0373 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs @@ -54,5 +54,15 @@ public class FindwiseConfig /// Gets or sets the index method. /// public string IndexMethod { get; set; } = null!; + + /// + /// Gets or sets the auto suggestion click component. + /// + public string UrlAutoSuggestionClickComponent { get; set; } = null!; + + /// + /// Gets or sets the url click component. + /// + public string UrlClickComponent { get; set; } = null!; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs index 8030cdf7d..327a0198b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs @@ -1,3 +1,5 @@ +using System; + namespace LearningHub.Nhs.OpenApi.Models.Configuration { /// @@ -55,6 +57,16 @@ public class LearningHubConfig /// public string ResourcePublishQueueRouteName { get; set; } = null!; + /// + /// Gets or sets . + /// + public string ContentManagementQueueName { get; set; } = null!; + + /// + /// Gets or sets . + /// + public DateTimeOffset DetailedMediaActivityRecordingStartDate { get; set; } + /// /// Gets or sets . /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourcePlayedSegmentRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourcePlayedSegmentRepository.cs new file mode 100644 index 000000000..248029c51 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourcePlayedSegmentRepository.cs @@ -0,0 +1,21 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The MediaResourcePlayedSegmentRepository interface. + /// + public interface IMediaResourcePlayedSegmentRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The userId. + /// The resourceId. + /// The majorVersion. + /// The . + Task> GetPlayedSegmentsAsync(int userId, int resourceId, int majorVersion); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Analytics/IEventRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Analytics/IEventRepository.cs new file mode 100644 index 000000000..ffb37eadc --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Analytics/IEventRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Analytics +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Analytics; + + /// + /// The IEventRepository interface. + /// + public interface IEventRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageRepository.cs new file mode 100644 index 000000000..6afccb71c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageRepository.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + + /// + /// The IPageRepository. + /// + public interface IPageRepository : IGenericRepository + { + /// + /// GetPagesAsync. + /// + /// A representing the result of the asynchronous operation. + Task> GetPagesAsync(); + + /// + /// The GetPageById. + /// + /// The id. + /// The published only. + /// Preview mode. + /// The . + Task GetPageByIdAsync(int id, bool publishedOnly, bool preview); + + /// + /// The DiscardAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + Task DiscardAsync(int pageId, int currentUserId); + + /// + /// The PublishAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + Task PublishAsync(int pageId, int currentUserId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionDetailRepository.cs new file mode 100644 index 000000000..894002835 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionDetailRepository.cs @@ -0,0 +1,51 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Enums.Content; + + /// + /// The IPageIdentifierRepository. + /// + public interface IPageSectionDetailRepository : IGenericRepository + { + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + Task GetPageSectionDetailImageAssetByIdAsync(int id); + + /// + /// The GetPageSectionDetailVideoAssetById. + /// + /// The id. + /// The . + Task GetPageSectionDetailVideoAssetByIdAsync(int id); + + /// + /// The create async. + /// + /// The user id. + /// The pageSectionDetail. + /// The . + new Task CreateAsync(int userId, PageSectionDetail pageSectionDetail); + + /// + /// The update async. + /// + /// The user id. + /// The pageSectionDetail. + /// The . + new Task UpdateAsync(int userId, PageSectionDetail pageSectionDetail); + + /// + /// The CloneSectionDetailAsync. + /// + /// The pageSectionDetailId. + /// sectionTemplateType. + /// currentUserId. + /// The . + Task CloneSectionDetailAsync(int pageSectionDetailId, SectionTemplateType sectionTemplateType, int currentUserId); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionRepository.cs new file mode 100644 index 000000000..04592bdf4 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IPageSectionRepository.cs @@ -0,0 +1,84 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + + /// + /// The IPageIdentifierRepository. + /// + public interface IPageSectionRepository : IGenericRepository + { + /// + /// The CreateWithPositionAsync. + /// + /// The userId. + /// The pageSection. + /// The . + Task CreateWithPositionAsync(int userId, PageSection pageSection); + + /// + /// The CloneImageSectionAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + Task CloneImageSectionAsync(int pageSectionId, int currentUserId); + + /// + /// The CloneVideoSectionAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + Task CloneVideoSectionAsync(int pageSectionId, int currentUserId); + + /// + /// The HideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task HideAsync(int pageSectionId, int currentUserId); + + /// + /// The UnHideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task UnHideAsync(int pageSectionId, int currentUserId); + + /// + /// The DeleteAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task DeleteAsync(int pageSectionId, int currentUserId); + + /// + /// The ChangeOrderAsync. + /// + /// The pageId. + /// The pageSectionId. + /// The currentUserId. + /// The . + Task ChangeOrderUpAsync(int pageId, int pageSectionId, int currentUserId); + + /// + /// The ChangeOrderAsync. + /// + /// The pageId. + /// The pageSectionId. + /// The currentUserId. + /// The . + Task ChangeOrderDownAsync(int pageId, int pageSectionId, int currentUserId); + + /// + /// The GetByIdAsync. + /// + /// The pageSectionId. + /// The . + Task GetByIdAsync(int pageSectionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IVideoAssetRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IVideoAssetRepository.cs new file mode 100644 index 000000000..086de3a62 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Content/IVideoAssetRepository.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + + /// + /// Defines the . + /// + public interface IVideoAssetRepository : IGenericRepository + { + /// + /// The GetByPageSectionDetailId. + /// + /// The pageSectionDetailId. + /// The . + Task GetByPageSectionDetailId(int pageSectionDetailId); + + /// + /// The GetByPageSectionDetailId. + /// + /// The videoAssetId. + /// The . + Task GetById(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEventLogRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEventLogRepository.cs new file mode 100644 index 000000000..ba932f891 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEventLogRepository.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using LearningHub.Nhs.Models.Enums; + + /// + /// The IEventLogRepository interface. + /// + public interface IEventLogRepository + { + /// + /// Create event. + /// + /// eventLog. + /// eventType. + /// hierarchyEditId. + /// nodeId. + /// resourceVersionId. + /// details. + /// user id. + void CreateEvent(EventLogEnum eventLog, EventLogEventTypeEnum eventType, int? hierarchyEditId, int? nodeId, int? resourceVersionId, string details, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs index f340ce670..171adfe9e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs @@ -55,6 +55,17 @@ Task> GetResourceReferencesByOriginalResourceRefe /// If the user has any resources published. Task UserHasPublishedResourcesAsync(int userId); + /// + /// The transfer resource ownership. + /// + /// The resource id. + /// The new owner username. + /// The user id. + void TransferResourceOwnership( + int resourceId, + string newOwnerUsername, + int userId); + /// /// The create resource async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IScopeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IScopeRepository.cs new file mode 100644 index 000000000..68a28f4d1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IScopeRepository.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The IScopeRepository interface. + /// + public interface IScopeRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by catalogueNodeId async. + /// + /// The id. + /// The . + Task GetByCatalogueNodeIdAsync(int? catalogueNodeId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupAttributeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupAttributeRepository.cs new file mode 100644 index 000000000..17915d64c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupAttributeRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The UserGroupAttributeRepository interface. + /// + public interface IUserGroupAttributeRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by user group id async. + /// + /// The user group id. + /// The attribute id. + /// The . + Task GetByUserGroupIdAttributeId(int userGroupId, int attributeId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupRepository.cs new file mode 100644 index 000000000..dd40139f8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserGroupRepository.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The UserGroupRepository interface. + /// + public interface IUserGroupRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by name async. + /// + /// The name. + /// The . + Task GetByNameAsync(string name); + + /// + /// The get by id async. + /// + /// The id. + /// The include roles. + /// The . + Task GetByIdAsync(int id, bool includeRoles); + + /// + /// The delete async. + /// + /// The user id. + /// The user group id. + /// The . + Task DeleteAsync(int userId, int userGroupId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProviderRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProviderRepository.cs new file mode 100644 index 000000000..a914e9185 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProviderRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Provider; + + /// + /// The UserProviderRepository interface. + /// + public interface IUserProviderRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get user provider list by user id async. + /// + /// The user id. + /// The . + Task> GetByUserIdAsync(int userId); + + /// + /// The update provider list by user id async. + /// + /// The user provider update model. + /// The . + Task UpdateUserProviderAsync(UserProviderUpdateViewModel userProviderUpdateModel); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionEventRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionEventRepository.cs new file mode 100644 index 000000000..a2d38810f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionEventRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Linq; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + + /// + /// The ResourceVersionEventRepository interface. + /// + public interface IResourceVersionEventRepository : IGenericRepository + { + /// + /// The get by resource version id async. + /// + /// The resourceVersionId. + /// The . + IQueryable GetByResourceVersionIdAsync(int resourceVersionId); + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The resource version event type. + /// The . + IQueryable GetByResourceVersionIdAndEventTypeAsync(int resourceVersionId, ResourceVersionEventTypeEnum resourceVersionEventType); + } + + /// + /// Defines the . + /// + public interface IResourceReferenceEventRepository : IGenericRepository + { + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index 680d27ddb..47302c970 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -717,6 +717,11 @@ public LearningHubDbContextOptions Options /// public virtual DbSet Provider { get; set; } + /// + /// Gets or sets User Provider. + /// + public virtual DbSet UserProvider { get; set; } + /// /// Gets or sets Resource Version Provider. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 1e4b39004..2464e5953 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -4,6 +4,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.Models.Automapper; using LearningHub.Nhs.OpenApi.Repositories.Map; using LearningHub.Nhs.OpenApi.Repositories.Map.Activity; + using LearningHub.Nhs.OpenApi.Repositories.Map.Analytics; using LearningHub.Nhs.OpenApi.Repositories.Map.Content; using LearningHub.Nhs.OpenApi.Repositories.Map.External; using LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy; @@ -84,6 +85,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Analytics/EventMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Analytics/EventMap.cs index 243528e22..5adae4cdf 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Analytics/EventMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Analytics/EventMap.cs @@ -1,6 +1,5 @@ -namespace LearningHub.Nhs.OpenApi.Repositories.Map +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Analytics { - using LearningHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.Analytics; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserProviderMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserProviderMap.cs new file mode 100644 index 000000000..85c9fb0f2 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserProviderMap.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map +{ + using LearningHub.Nhs.Models.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The user provider map. + /// + public class UserProviderMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("UserProvider", "hub"); + + modelBuilder.HasKey(e => e.Id); + + modelBuilder.Property(e => e.Id) + .HasColumnName("Id"); + modelBuilder.Property(e => e.UserId).HasColumnName("UserId"); + modelBuilder.Property(e => e.ProviderId).HasColumnName("ProviderId"); + modelBuilder.Property(e => e.RemovalDate).HasColumnName("RemovalDate"); + + modelBuilder.HasOne(d => d.User) + .WithMany(p => p.UserProvider) + .HasForeignKey(d => d.UserId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_userProvider_user"); + + modelBuilder.HasOne(d => d.Provider) + .WithMany(p => p.UserProvider) + .HasForeignKey(d => d.ProviderId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_userProvider_provider"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourcePlayedSegmentRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourcePlayedSegmentRepository.cs new file mode 100644 index 000000000..fbfca7194 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourcePlayedSegmentRepository.cs @@ -0,0 +1,42 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.EntityFrameworkCore; + + /// + /// The activity repository. + /// + public class MediaResourcePlayedSegmentRepository : GenericRepository, IMediaResourcePlayedSegmentRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public MediaResourcePlayedSegmentRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The userId. + /// The resourceId. + /// The majorVersion. + /// The . + public async Task> GetPlayedSegmentsAsync(int userId, int resourceId, int majorVersion) + { + return await DbContext.MediaResourcePlayedSegment + .Where(x => x.UserId == userId && x.ResourceId == resourceId && x.MajorVersion == majorVersion) + .OrderBy(x => x.SegmentStartTime) + .ToListAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Analytics/EventRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Analytics/EventRepository.cs new file mode 100644 index 000000000..104dc8ff8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Analytics/EventRepository.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Analytics +{ + using System; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Analytics; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Analytics; + using Microsoft.EntityFrameworkCore; + + /// + /// The event repository. + /// + public class EventRepository : GenericRepository, IEventRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public EventRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// Get event by id. + /// + /// The id. + /// The task. + public async Task GetByIdAsync(int id) + { + try + { + return await DbContext.Event.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageRepository.cs new file mode 100644 index 000000000..da1e33c70 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageRepository.cs @@ -0,0 +1,233 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Content +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Enums.Content; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; + using Microsoft.EntityFrameworkCore; + + /// + /// The PageRepository. + /// + public class PageRepository : GenericRepository, IPageRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public PageRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The DiscardAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + public async Task DiscardAsync(int pageId, int currentUserId) + { + var page = await DbContext.Page.Include(p => p.PageSections).ThenInclude(ps => ps.PageSectionDetails) + .Where(p => p.Id == pageId).SingleAsync(); + + var discardStatusColl = new List { PageSectionStatus.Draft, PageSectionStatus.Processing, PageSectionStatus.ProcessingFailed, PageSectionStatus.Processed }; + + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + foreach (var pageSection in page.PageSections) + { + foreach (var pageSectionDetail in pageSection.PageSectionDetails.Where(psd => psd.Deleted == false)) + { + if (discardStatusColl.Contains((PageSectionStatus)pageSectionDetail.PageSectionStatusId)) + { + pageSectionDetail.Deleted = true; + SetAuditFieldsForUpdate(currentUserId, pageSectionDetail); + } + } + + if (pageSection.PageSectionDetails.All(psd => psd.Deleted)) + { + SetAuditFieldsForUpdate(currentUserId, pageSection); + pageSection.Deleted = true; + } + } + + SetAuditFieldsForUpdate(currentUserId, page); + await DbContext.SaveChangesAsync(); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The GetPageById. + /// + /// The id. + /// The published only. + /// Preview mode. + /// The . + public async Task GetPageByIdAsync(int id, bool publishedOnly, bool preview) + { + var pageData = await DbContext.Page + .Include(p => p.PageSections) + .ThenInclude(p => p.PageSectionDetails) + .ThenInclude(s => s.ImageAsset) + .ThenInclude(s => s.ImageFile) + .Where(p => p.Id == id) + .SingleAsync(); + + if (publishedOnly) + { + foreach (var section in pageData.PageSections) + { + section.PageSectionDetails.Where(p => p.PageSectionStatusId != (int)PageSectionStatus.Live).ToList() + .ForEach(e => section.PageSectionDetails.Remove(e)); + } + + pageData.PageSections.Where(p => p.PageSectionDetails.Count == 0).ToList() + .ForEach(e => pageData.PageSections.Remove(e)); + } + + if (preview) + { + var removeDeleted = pageData.PageSections.Where(ps => !ps.PageSectionDetails.Any(psd => psd.DeletePending.HasValue && psd.DeletePending.Value)).ToList(); + pageData.PageSections = removeDeleted; + } + + return new Page + { + Id = pageData.Id, + Name = pageData.Name, + Url = pageData.Url, + PageSections = pageData.PageSections.Where(ps => ps.Deleted == false).Select(ps => + new PageSection + { + Id = ps.Id, + IsHidden = ps.IsHidden, + Position = ps.Position, + AmendUserId = ps.AmendUserId, + SectionTemplateTypeId = ps.SectionTemplateTypeId, + PageSectionDetails = ps.PageSectionDetails.Where(psd => psd.Deleted == false).OrderByDescending(psd => psd.Id).Take(1).ToList(), + }).ToList(), + }; + } + + /// + /// The GetPagesAsync. + /// + /// The . + public async Task> GetPagesAsync() + { + return await DbContext.Page.Select(p => + new Page + { + Id = p.Id, + Name = p.Name, + Url = p.Url, + PageSections = p.PageSections.Where(ps => ps.Deleted == false).Select(ps => + new PageSection + { + PageSectionDetails = ps.PageSectionDetails.Where(psd => psd.Deleted == false).OrderByDescending(psd => psd.Id).ToList(), + }).ToList(), + }).ToListAsync(); + } + + /// + /// The PublishAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + public async Task PublishAsync(int pageId, int currentUserId) + { + var page = await DbContext.Page.Include(p => p.PageSections).ThenInclude(ps => ps.PageSectionDetails.OrderBy(psd => psd.PageSectionStatusId)) + .Where(p => p.Id == pageId).SingleAsync(); + + var publishStatusColl = new List { PageSectionStatus.Processed }; + + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + foreach (var pageSection in page.PageSections) + { + var detailSetToLive = false; + foreach (var pageSectionDetail in pageSection.PageSectionDetails.Where(psd => psd.Deleted == false)) + { + if (detailSetToLive && (PageSectionStatus)pageSectionDetail.PageSectionStatusId == PageSectionStatus.Live) + { + pageSectionDetail.Deleted = true; + SetAuditFieldsForUpdate(currentUserId, pageSectionDetail); + } + + if (publishStatusColl.Contains((PageSectionStatus)pageSectionDetail.PageSectionStatusId)) + { + pageSectionDetail.PageSectionStatusId = (int)PageSectionStatus.Live; + detailSetToLive = true; + SetAuditFieldsForUpdate(currentUserId, pageSectionDetail); + } + + if (pageSectionDetail.PageSectionStatusId == (int)PageSectionStatus.Draft) + { + if (pageSectionDetail.DraftHidden.HasValue) + { + pageSection.IsHidden = pageSectionDetail.DraftHidden.Value; + } + + if (pageSectionDetail.DraftPosition.HasValue) + { + pageSection.Position = pageSectionDetail.DraftPosition.Value; + } + + if (pageSectionDetail.DeletePending.HasValue && pageSectionDetail.DeletePending.Value) + { + pageSection.Deleted = true; + } + + pageSectionDetail.DraftHidden = null; + pageSectionDetail.DraftPosition = null; + pageSectionDetail.DeletePending = null; + + pageSectionDetail.PageSectionStatusId = (int)PageSectionStatus.Live; + SetAuditFieldsForUpdate(currentUserId, pageSectionDetail); + SetAuditFieldsForUpdate(currentUserId, pageSection); + } + } + } + + SetAuditFieldsForUpdate(currentUserId, page); + await DbContext.SaveChangesAsync(); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs new file mode 100644 index 000000000..04ecb1258 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs @@ -0,0 +1,127 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Content +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Enums.Content; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; + using Microsoft.EntityFrameworkCore; + + /// + /// The PageSectionDetailRepository. + /// + public class PageSectionDetailRepository : GenericRepository, IPageSectionDetailRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public PageSectionDetailRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + public async Task GetPageSectionDetailImageAssetByIdAsync(int id) + { + return await DbContext.PageSectionDetail + .Include(s => s.ImageAsset).ThenInclude(s => s.ImageFile) + .SingleOrDefaultAsync(p => p.Id == id); + } + + /// + /// The GetPageSectionDetailVideoAssetByIdAsync. + /// + /// The id. + /// The . + public async Task GetPageSectionDetailVideoAssetByIdAsync(int id) + { + return await DbContext.PageSectionDetail + .Include(v => v.VideoAsset).ThenInclude(v => v.VideoFile) + .Include(v => v.VideoAsset).ThenInclude(a => a.AzureMediaAsset) + .Include(v => v.VideoAsset).ThenInclude(t => t.TranscriptFile) + .Include(v => v.VideoAsset).ThenInclude(c => c.ClosedCaptionsFile) + .Include(v => v.VideoAsset).ThenInclude(t => t.ThumbnailImageFile) + .SingleOrDefaultAsync(psd => psd.Id == id); + } + + /// + /// The create async. + /// + /// The user id. + /// The pageSectionDetail. + /// The . + public async new Task CreateAsync(int userId, PageSectionDetail pageSectionDetail) + { + if (pageSectionDetail.ImageAsset != null) + { + SetAuditFieldsForCreate(userId, pageSectionDetail.ImageAsset); + } + + return await base.CreateAsync(userId, pageSectionDetail); + } + + /// + /// The update async. + /// + /// The user id. + /// The pageSectionDetail. + /// The . + public async new Task UpdateAsync(int userId, PageSectionDetail pageSectionDetail) + { + if (pageSectionDetail.ImageAsset != null) + { + SetAuditFieldsForUpdate(userId, pageSectionDetail.ImageAsset); + } + + await base.UpdateAsync(userId, pageSectionDetail); + } + + /// + public async Task CloneSectionDetailAsync(int pageSectionDetailId, SectionTemplateType sectionTemplateType, int currentUserId) + { + PageSectionDetail pageSectionDetail = null; + + if (sectionTemplateType == SectionTemplateType.Video) + { + pageSectionDetail = await DbContext.PageSectionDetail.Where(ps => ps.Id == pageSectionDetailId).AsNoTracking().Include(psd => psd.VideoAsset).AsNoTracking().SingleAsync(); + var videoAsset = pageSectionDetail.VideoAsset; + videoAsset.Id = 0; + pageSectionDetail.VideoAsset = videoAsset; + } + else + { + pageSectionDetail = await DbContext.PageSectionDetail.Where(ps => ps.Id == pageSectionDetailId).AsNoTracking().Include(psd => psd.ImageAsset).AsNoTracking().SingleAsync(); + var imageAsset = pageSectionDetail.ImageAsset; + imageAsset.Id = 0; + pageSectionDetail.ImageAsset = imageAsset; + } + + pageSectionDetail.Id = 0; + + // The new page section must be made in a status of draft + pageSectionDetail.PageSectionStatusId = (int)PageSectionStatus.Draft; + + pageSectionDetail.Id = await CreateAsync(currentUserId, pageSectionDetail); + + if (sectionTemplateType == SectionTemplateType.Video) + { + pageSectionDetail = await GetPageSectionDetailVideoAssetByIdAsync(pageSectionDetail.Id); + } + else + { + pageSectionDetail = await GetPageSectionDetailImageAssetByIdAsync(pageSectionDetail.Id); + } + + return pageSectionDetail; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionRepository.cs new file mode 100644 index 000000000..a2d561b83 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionRepository.cs @@ -0,0 +1,546 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Content +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Enums.Content; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; + using Microsoft.EntityFrameworkCore; + + /// + /// The PageSectionRepository. + /// + public class PageSectionRepository : GenericRepository, IPageSectionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public PageSectionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The create async. + /// + /// The user id. + /// The PageSection. + /// The . + public async Task CreateWithPositionAsync(int userId, PageSection pageSection) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + var pageSectionId = 0; + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + foreach (var section in DbContext.PageSection.Where(p => p.Position >= pageSection.Position && p.PageId == pageSection.PageId)) + { + section.Position += 1; + SetAuditFieldsForUpdate(userId, section); + } + + pageSectionId = await CreateAsync(userId, pageSection); + + transaction.Commit(); + + return pageSectionId; + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + + return pageSectionId; + } + + /// + /// The CloneImageSectionAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + public async Task CloneImageSectionAsync(int pageSectionId, int currentUserId) + { + var pageSection = await DbContext.PageSection.Where(ps => ps.Id == pageSectionId).AsNoTracking().Include(p => p.PageSectionDetails).ThenInclude(psd => psd.ImageAsset).AsNoTracking().SingleAsync(); + pageSection.Id = 0; + var pageSectionDetail = pageSection.PageSectionDetails.Where(psd => psd.Deleted == false).OrderByDescending(psd => psd.Id).Take(1).Single(); + pageSectionDetail.Id = 0; + SetAuditFieldsForCreate(currentUserId, pageSectionDetail); + + var imageAsset = pageSectionDetail.ImageAsset; + imageAsset.Id = 0; + SetAuditFieldsForCreate(currentUserId, imageAsset); + pageSectionDetail.ImageAsset = imageAsset; + + // The new page section must be made in a status of draft + pageSectionDetail.PageSectionStatusId = 1; + + pageSection.PageSectionDetails = new List + { + pageSectionDetail, + }; + SetAuditFieldsForCreate(currentUserId, pageSection); + + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var pageSections = await DbContext.PageSection + .Where(ps => ps.PageId == pageSection.PageId && ps.Deleted == false) + .OrderBy(ps => ps.Position).ToListAsync(); + var pageSectionIndex = pageSections.FindIndex(ps => ps.Id == pageSectionId); + for (int i = pageSectionIndex; i <= pageSections.Count() - 1; i++) + { + pageSections[i].Position += 1; + SetAuditFieldsForUpdate(currentUserId, pageSections[i]); + } + + await CreateAsync(currentUserId, pageSection); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The CloneVideoSectionAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + public async Task CloneVideoSectionAsync(int pageSectionId, int currentUserId) + { + var pageSection = await DbContext.PageSection.Where(ps => ps.Id == pageSectionId).AsNoTracking().Include(p => p.PageSectionDetails).ThenInclude(psd => psd.VideoAsset).AsNoTracking().SingleAsync(); + pageSection.Id = 0; + var pageSectionDetail = pageSection.PageSectionDetails.Where(psd => psd.Deleted == false).OrderByDescending(psd => psd.Id).Take(1).Single(); + pageSectionDetail.Id = 0; + SetAuditFieldsForCreate(currentUserId, pageSectionDetail); + + var videoAsset = pageSectionDetail.VideoAsset; + videoAsset.Id = 0; + SetAuditFieldsForCreate(currentUserId, videoAsset); + pageSectionDetail.VideoAsset = videoAsset; + + // The new page section must be made in a status of draft + pageSectionDetail.PageSectionStatusId = 1; + + pageSection.PageSectionDetails = new List + { + pageSectionDetail, + }; + SetAuditFieldsForCreate(currentUserId, pageSection); + + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var pageSections = await DbContext.PageSection.Where(ps => ps.PageId == pageSection.PageId && ps.Deleted == false).OrderBy(ps => ps.Position).ToListAsync(); + var pageSectionIndex = pageSections.FindIndex(ps => ps.Id == pageSectionId); + for (int i = pageSectionIndex; i <= pageSections.Count() - 1; i++) + { + pageSections[i].Position += 1; + SetAuditFieldsForUpdate(currentUserId, pageSections[i]); + } + + await CreateAsync(currentUserId, pageSection); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The ChangeOrderUpAsync. + /// + /// The pageId. + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task ChangeOrderUpAsync(int pageId, int pageSectionId, int currentUserId) + { + var pageSections = DbContext.PageSection + .Include(x => x.PageSectionDetails) + .Where(ps => ps.PageId == pageId && !ps.Deleted && !ps.IsHidden) + .ToList() + .OrderBy(ps => + { + var latestDetails = ps.PageSectionDetails.OrderByDescending(x => x.Id).First(); + if (latestDetails.PageSectionStatusId == (int)PageSectionStatus.Draft + && latestDetails.DraftPosition != null) + { + return latestDetails.DraftPosition; + } + + return ps.Position; + }).ToList(); + + var pageSectionIndex = pageSections.FindIndex(ps => ps.Id == pageSectionId); + + if (pageSections.Count >= pageSectionIndex + 1) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var moveDownSection = pageSections[pageSectionIndex + 1]; + var moveUpSection = pageSections[pageSectionIndex]; + + var (moveDownSectionDetails, moveDownExistingDraft) = await EnsureDraft(moveDownSection.Id, currentUserId); + var (moveUpSectionDetails, moveUpExistingDraft) = await EnsureDraft(moveUpSection.Id, currentUserId); + + var currentMoveDownPosition = moveDownExistingDraft + ? moveDownSectionDetails.DraftPosition ?? moveDownSection.Position + : moveDownSection.Position; + + var currentMoveUpPosition = moveUpExistingDraft + ? moveUpSectionDetails.DraftPosition ?? moveUpSection.Position + : moveUpSection.Position; + + moveDownSectionDetails.DraftPosition = currentMoveDownPosition - 1; + + if (moveDownExistingDraft) + { + SetAuditFieldsForUpdate(currentUserId, moveDownSectionDetails); + } + + moveUpSectionDetails.DraftPosition = currentMoveUpPosition + 1; + + if (moveUpExistingDraft) + { + SetAuditFieldsForUpdate(currentUserId, moveUpSectionDetails); + } + + await DbContext.SaveChangesAsync(); + if (moveDownExistingDraft) + { + DbContext.Entry(moveDownSectionDetails).State = EntityState.Detached; + } + + if (moveUpExistingDraft) + { + DbContext.Entry(moveUpSectionDetails).State = EntityState.Detached; + } + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + } + + /// + /// The ChangeOrderUpAsync. + /// + /// The pageId. + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task ChangeOrderDownAsync(int pageId, int pageSectionId, int currentUserId) + { + var pageSections = DbContext.PageSection + .Include(x => x.PageSectionDetails) + .Where(ps => ps.PageId == pageId && !ps.Deleted && !ps.IsHidden) + .ToList() + .OrderBy(ps => + { + var latestDetails = ps.PageSectionDetails.OrderByDescending(x => x.Id).First(); + if (latestDetails.PageSectionStatusId == (int)PageSectionStatus.Draft + && latestDetails.DraftPosition != null) + { + return latestDetails.DraftPosition; + } + + return ps.Position; + }).ToList(); + + var pageSectionIndex = pageSections.FindIndex(ps => ps.Id == pageSectionId); + + if (pageSectionIndex > 0) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var moveDownSection = pageSections[pageSectionIndex]; + var moveUpSection = pageSections[pageSectionIndex - 1]; + + var (moveDownSectionDetails, moveDownExistingDraft) = await EnsureDraft(moveDownSection.Id, currentUserId); + var (moveUpSectionDetails, moveUpExistingDraft) = await EnsureDraft(moveUpSection.Id, currentUserId); + + var currentMoveDownPosition = moveDownExistingDraft + ? moveDownSectionDetails.DraftPosition ?? moveDownSection.Position + : moveDownSection.Position; + + var currentMoveUpPosition = moveUpExistingDraft + ? moveUpSectionDetails.DraftPosition ?? moveUpSection.Position + : moveUpSection.Position; + + moveDownSectionDetails.DraftPosition = currentMoveDownPosition - 1; + + if (moveDownExistingDraft) + { + SetAuditFieldsForUpdate(currentUserId, moveDownSectionDetails); + } + + moveUpSectionDetails.DraftPosition = currentMoveUpPosition + 1; + + if (moveUpExistingDraft) + { + SetAuditFieldsForUpdate(currentUserId, moveUpSectionDetails); + } + + await DbContext.SaveChangesAsync(); + if (moveDownExistingDraft) + { + DbContext.Entry(moveDownSectionDetails).State = EntityState.Detached; + } + + if (moveUpExistingDraft) + { + DbContext.Entry(moveUpSectionDetails).State = EntityState.Detached; + } + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + } + + /// + /// The HideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task HideAsync(int pageSectionId, int currentUserId) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var (pageSectionDetails, ed) = await EnsureDraft(pageSectionId, currentUserId); + pageSectionDetails.DraftHidden = true; + if (ed) + { + SetAuditFieldsForUpdate(currentUserId, pageSectionDetails); + } + + await DbContext.SaveChangesAsync(); + if (ed) + { + DbContext.Entry(pageSectionDetails).State = EntityState.Detached; + } + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The UnHideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task UnHideAsync(int pageSectionId, int currentUserId) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var (pageSectionDetails, ed) = await EnsureDraft(pageSectionId, currentUserId); + pageSectionDetails.DraftHidden = false; + if (ed) + { + SetAuditFieldsForUpdate(currentUserId, pageSectionDetails); + } + + await DbContext.SaveChangesAsync(); + + if (ed) + { + DbContext.Entry(pageSectionDetails).State = EntityState.Detached; + } + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The DeleteAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task DeleteAsync(int pageSectionId, int currentUserId) + { + var strategy = DbContext.Database.CreateExecutionStrategy(); + await strategy.Execute( + async () => + { + using (var transaction = DbContext.Database.BeginTransaction()) + { + try + { + var (pageSectionDetails, ed) = await EnsureDraft(pageSectionId, currentUserId); + pageSectionDetails.DeletePending = true; + if (ed) + { + DbContext.Set().Update(pageSectionDetails); + SetAuditFieldsForUpdate(currentUserId, pageSectionDetails); + } + + await DbContext.SaveChangesAsync(); + if (ed) + { + DbContext.Entry(pageSectionDetails).State = EntityState.Detached; + } + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + }); + } + + /// + /// The GetByIdAsync. + /// + /// The pageSectionId. + /// The . + public async Task GetByIdAsync(int pageSectionId) + { + return await DbContext.PageSection + .Include(ps => ps.PageSectionDetails) + .ThenInclude(s => s.ImageAsset).ThenInclude(s => s.ImageFile) + .SingleAsync(ps => ps.Id == pageSectionId); + } + + /// + /// The EnsureDraft method. This makes sure the latest PageSectionDetails for the PageSection + /// is a draft, and creates a new draft if not. + /// Used when moving/hiding/etc PageSections, so that the PageSectionDetails which + /// are updated are not the Live versions. + /// + /// The task. + private async Task<(PageSectionDetail, bool)> EnsureDraft(int pageSectionId, int currentUserId) + { + var pageSection = DbContext.PageSection + .Where(ps => ps.Id == pageSectionId) + .Include(p => p.PageSectionDetails).ThenInclude(x => x.VideoAsset) + .Include(p => p.PageSectionDetails).ThenInclude(x => x.ImageAsset) + .Single(); + + var pageSectionDetail = pageSection.PageSectionDetails + .Where(psd => psd.Deleted == false) + .OrderByDescending(psd => psd.Id).Take(1).Single(); + + var existingDraft = true; + if (pageSectionDetail.PageSectionStatusId != (int)PageSectionStatus.Draft) + { + DbContext.Entry(pageSectionDetail).State = EntityState.Detached; + pageSectionDetail.Id = 0; + pageSectionDetail.PageSectionStatusId = (int)PageSectionStatus.Draft; + + if (pageSectionDetail.VideoAsset != null) + { + var va = pageSectionDetail.VideoAsset; + DbContext.Entry(va).State = EntityState.Detached; + va.Id = 0; + SetAuditFieldsForCreate(currentUserId, va); + pageSectionDetail.VideoAsset = va; + } + + if (pageSectionDetail.ImageAsset != null) + { + var ia = pageSectionDetail.ImageAsset; + DbContext.Entry(ia).State = EntityState.Detached; + ia.Id = 0; + SetAuditFieldsForCreate(currentUserId, ia); + pageSectionDetail.ImageAsset = ia; + } + + await DbContext.Set().AddAsync(pageSectionDetail); + SetAuditFieldsForCreate(currentUserId, pageSectionDetail); + + existingDraft = false; + } + + return (pageSectionDetail, existingDraft); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/VideoAssetRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/VideoAssetRepository.cs new file mode 100644 index 000000000..b70558169 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/VideoAssetRepository.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Content +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; + using Microsoft.EntityFrameworkCore; + + /// + /// Defines the . + /// + public class VideoAssetRepository : GenericRepository, IVideoAssetRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public VideoAssetRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The GetByPageSectionDetailId. + /// + /// The pageSectionDetailId. + /// The . + public async Task GetByPageSectionDetailId(int pageSectionDetailId) + { + return await DbContext.VideoAsset.SingleOrDefaultAsync(va => va.PageSectionDetailId == pageSectionDetailId); + } + + /// + /// The GetById. + /// + /// The id. + /// The . + public async Task GetById(int id) + { + return await DbContext.VideoAsset.SingleOrDefaultAsync(va => va.Id == id); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EventLogRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EventLogRepository.cs new file mode 100644 index 000000000..074482938 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EventLogRepository.cs @@ -0,0 +1,80 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + using System.Data; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The event log repository. + /// + public class EventLogRepository : IEventLogRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + public EventLogRepository(LearningHubDbContext dbContext) + { + DbContext = dbContext; + } + + /// + /// Gets the db context. + /// + protected LearningHubDbContext DbContext { get; } + + /// + /// Create event. + /// + /// eventLog. + /// eventType. + /// hierarchyEditId. + /// nodeId. + /// resourceVersionId. + /// details. + /// user id. + public void CreateEvent(EventLogEnum eventLog, EventLogEventTypeEnum eventType, int? hierarchyEditId, int? nodeId, int? resourceVersionId, string details, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = (int)eventLog }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = (int)eventType }; + SqlParameter param2; + if (hierarchyEditId.HasValue) + { + param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = hierarchyEditId.Value }; + } + else + { + param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = DBNull.Value }; + } + + SqlParameter param3; + if (nodeId.HasValue) + { + param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = nodeId.Value }; + } + else + { + param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = DBNull.Value }; + } + + SqlParameter param4; + if (resourceVersionId.HasValue) + { + param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = resourceVersionId.Value }; + } + else + { + param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = DBNull.Value }; + } + + var param5 = new SqlParameter("@p5", SqlDbType.NVarChar) { Value = details }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Value = userId }; + + DbContext.Database.ExecuteSqlRaw("hub.EventLogEventCreate @p0, @p1, @p2, @p3, @p4, @p5, @p6", param0, param1, param2, param3, param4, param5, param6); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs index a0004a6e2..ee2cc2714 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs @@ -185,6 +185,22 @@ public async Task GetByResourceVersionIdAsync(int resourceVersionId) .FirstOrDefaultAsync(); } + /// + /// The transfer resource ownership. + /// + /// The resource id. + /// The new owner username. + /// The user id. + public void TransferResourceOwnership(int resourceId, string newOwnerUsername, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = resourceId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = newOwnerUsername }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = this.TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceReassignOwnership @p0, @p1, @p2, @p3", param0, param1, param2, param3); + } + /// /// Returns a bool to indicate if the resourceVersionId corresponds to a current version of a resource. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionEventRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionEventRepository.cs new file mode 100644 index 000000000..071259220 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionEventRepository.cs @@ -0,0 +1,51 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.EntityFrameworkCore; + + /// + /// The resource version Event repository. + /// + public class ResourceVersionEventRepository : GenericRepository, IResourceVersionEventRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ResourceVersionEventRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + public IQueryable GetByResourceVersionIdAsync(int resourceVersionId) + { + return DbContext.ResourceVersionEvent.Where(r => r.ResourceVersionId == resourceVersionId && !r.Deleted).AsNoTracking() + .Include(r => r.CreateUser) + .OrderBy(r => r.Id) + .AsNoTracking(); + } + + /// + public IQueryable GetByResourceVersionIdAndEventTypeAsync(int resourceVersionId, ResourceVersionEventTypeEnum resourceVersionEventType) + { + return DbContext.ResourceVersionEvent.Where(r => r.ResourceVersionId == resourceVersionId + && r.ResourceVersionEventType == resourceVersionEventType + && !r.Deleted).AsNoTracking() + .Include(r => r.CreateUser) + .OrderBy(r => r.Id) + .AsNoTracking(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ScopeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ScopeRepository.cs new file mode 100644 index 000000000..d69d57d11 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ScopeRepository.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The scope repository. + /// + public class ScopeRepository : GenericRepository, IScopeRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ScopeRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.Scope.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); + } + + /// + /// The get by catalogueNodeId async. + /// + /// The id. + /// The . + public async Task GetByCatalogueNodeIdAsync(int? catalogueNodeId) + { + return await DbContext.Scope.AsNoTracking().FirstOrDefaultAsync(n => n.ScopeType == Nhs.Models.Enums.ScopeTypeEnum.Catalogue + && (catalogueNodeId.HasValue && n.CatalogueNodeId.Value == catalogueNodeId + || !catalogueNodeId.HasValue)); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupAttributeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupAttributeRepository.cs new file mode 100644 index 000000000..56fa6f8b0 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupAttributeRepository.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The user group attribute repository. + /// + public class UserGroupAttributeRepository : GenericRepository, IUserGroupAttributeRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public UserGroupAttributeRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.UserGroupAttribute.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); + } + + /// + /// The get by user group id async. + /// + /// The user group id. + /// The attribute id. + /// The . + public async Task GetByUserGroupIdAttributeId(int userGroupId, int attributeId) + { + return await DbContext.UserGroupAttribute.Where(n => n.UserGroupId == userGroupId && n.AttributeId == attributeId && n.Deleted == false).FirstOrDefaultAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupRepository.cs new file mode 100644 index 000000000..532c61c1d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserGroupRepository.cs @@ -0,0 +1,99 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The user group repository. + /// + public class UserGroupRepository : GenericRepository, IUserGroupRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public UserGroupRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.UserGroup.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); + } + + /// + /// The get by id async. + /// + /// The id. + /// The include roles. + /// The . + public async Task GetByIdAsync(int id, bool includeRoles) + { + if (includeRoles) + { + return await DbContext.UserGroup + .Include(r => r.RoleUserGroup).ThenInclude(rug => rug.Role) + .Include(r => r.RoleUserGroup) + .ThenInclude(rug => rug.Scope) + .ThenInclude(s => s.CatalogueNode) + .ThenInclude(s => s.CurrentNodeVersion) + .ThenInclude(s => s.CatalogueNodeVersion) + .AsNoTracking() + .FirstOrDefaultAsync(n => n.Id == id); + } + else + { + return await DbContext.UserGroup.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); + } + } + + /// + /// The get by name async. + /// + /// The name. + /// The . + public async Task GetByNameAsync(string name) + { + return await DbContext.UserGroup.AsNoTracking().FirstOrDefaultAsync(n => n.Name == name); + } + + /// + /// The delete async. + /// + /// The user id. + /// The user group id. + /// The . + public async Task DeleteAsync(int userId, int userGroupId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userGroupId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("hierarchy.UserGroupDelete @p0, @p1, @p2", param0, param1, param2); + } + + /// + /// The get all. + /// + /// The . + public new IQueryable GetAll() + { + return DbContext.Set() + .Include(n => n.RoleUserGroup).ThenInclude(n => n.Scope); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserProviderRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserProviderRepository.cs new file mode 100644 index 000000000..798609750 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserProviderRepository.cs @@ -0,0 +1,88 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Provider; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The user provider repository. + /// + public class UserProviderRepository : GenericRepository, IUserProviderRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public UserProviderRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + public async Task GetByIdAsync(int id) + { + return await DbContext.UserProvider.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id && !n.Deleted); + } + + /// + /// The get list by user id async. + /// + /// The user id. + /// The . + public async Task> GetByUserIdAsync(int userId) + { + return await DbContext.UserProvider.Include(n => n.Provider).AsNoTracking() + .Where(n => n.UserId == userId && !n.Deleted).ToListAsync(); + } + + /// + /// The update provider list by user id async. + /// + /// The user provider update model. + /// The . + public async Task UpdateUserProviderAsync(UserProviderUpdateViewModel userProviderUpdateModel) + { + var existingUserProviders = DbContext.UserProvider.Where(n => n.UserId == userProviderUpdateModel.UserId); + + // Delete User Providers + foreach (var existingUserProvider in existingUserProviders) + { + if (userProviderUpdateModel.ProviderIds == null || !userProviderUpdateModel.ProviderIds.Contains(existingUserProvider.ProviderId)) + { + this.SetAuditFieldsForDelete(userProviderUpdateModel.UserId, existingUserProvider); + existingUserProvider.RemovalDate = existingUserProvider.AmendDate; + } + } + + // Insert User Provider + if (userProviderUpdateModel.ProviderIds != null) + { + foreach (var providerId in userProviderUpdateModel.ProviderIds) + { + var existingUserProvider = existingUserProviders.Where(n => n.ProviderId == providerId).SingleOrDefault(); + + if (existingUserProvider == null) + { + var userProvider = new UserProvider() + { + UserId = userProviderUpdateModel.UserId, + ProviderId = providerId, + }; + + SetAuditFieldsForCreate(userProviderUpdateModel.UserId, userProvider); + DbContext.UserProvider.Add(userProvider); + } + } + } + + await DbContext.SaveChangesAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs index 3a32a35f6..c181b1092 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs @@ -3,6 +3,8 @@ namespace LearningHub.Nhs.OpenApi.Repositories using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Analytics; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Messaging; @@ -10,6 +12,8 @@ namespace LearningHub.Nhs.OpenApi.Repositories using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; using LearningHub.Nhs.OpenApi.Repositories.Repositories; using LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity; + using LearningHub.Nhs.OpenApi.Repositories.Repositories.Analytics; + using LearningHub.Nhs.OpenApi.Repositories.Repositories.Content; using LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy; using LearningHub.Nhs.OpenApi.Repositories.Repositories.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Repositories.Messaging; @@ -41,6 +45,8 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -51,8 +57,10 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Resources services.AddScoped(); @@ -86,6 +94,7 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Activity services.AddScoped(); @@ -95,6 +104,7 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -114,6 +124,16 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + + // Event + services.AddScoped(); + services.AddScoped(); + + // Content + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IBookmarkService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IBookmarkService.cs index bfd212c64..3302ac654 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IBookmarkService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IBookmarkService.cs @@ -16,6 +16,15 @@ public interface IBookmarkService /// IEnumerable BookmarkViewModel. Task> GetAllByParent(string authHeader); + /// + /// The GetAllByParent. + /// + /// The currentUserId. + /// The parentId. + /// The all. + /// The . + Task> GetAllByParent(int currentUserId, int? parentId, bool? all = false); + /// /// The Create. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs index 29dbfb173..ee252f473 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs @@ -13,6 +13,13 @@ /// public interface ICatalogueService { + /// + /// The Get Basic Catalogue. + /// + /// The catalogueNode. + /// The catalogues. + CatalogueBasicViewModel GetBasicCatalogue(int catalogueNodeId); + /// /// get all catalogues async. /// @@ -89,6 +96,14 @@ public interface ICatalogueService /// The . Task CanUserEditCatalogueAsync(int userId, int catalogueId); + + /// + /// The GetCatalogues. + /// + /// The catalogueIds. + /// A representing the result of the asynchronous operation. + Task> GetCatalogues(List catalogueIds); + /// /// Returns basic catalogues for user. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs new file mode 100644 index 000000000..17213a4fb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs @@ -0,0 +1,38 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Dashboard; + + /// + /// IDashboardService. + /// + public interface IDashboardService + { + /// + /// GetMyAccessLearnings. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// A representing the result of the asynchronous operation. + Task GetMyAccessLearnings(string dashboardType, int pageNumber, int userId); + + /// + /// GetResources. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// A representing the result of the asynchronous operation. + Task GetCatalogues(string dashboardType, int pageNumber, int userId); + + /// + /// GetCataloguessAsync. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// A representing the result of the asynchronous operation. + Task GetResources(string dashboardType, int pageNumber, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventLogService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventLogService.cs new file mode 100644 index 000000000..7016ef2fd --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventLogService.cs @@ -0,0 +1,16 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using LearningHub.Nhs.Models.EventLog; + + /// + /// The IEventLogService interface. + /// + public interface IEventLogService + { + /// + /// Create event. + /// + /// eventViewModel. + void CreateEvent(EventViewModel eventViewModel); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventService.cs new file mode 100644 index 000000000..e78227b98 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IEventService.cs @@ -0,0 +1,35 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Analytics; + using LearningHub.Nhs.Models.Validation; + + /// + /// The RoleService interface. + /// + public interface IEventService + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The save async. + /// + /// The user id. + /// The event view model. + /// The . + Task CreateAsync(int userId, Event eventEntity); + + /// + /// The update async. + /// + /// The user id. + /// The event entity. + /// The . + Task UpdateAsync(int userId, Event eventEntity); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs new file mode 100644 index 000000000..b72447a00 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs @@ -0,0 +1,49 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.Models.MyLearning; + + /// + /// The MyLearningService interface. + /// + public interface IMyLearningService + { + /// + /// Gets the activity records for the detailed activity tab of My Learning screen. + /// + /// /// The user id. + /// The request model. + /// The . + Task GetActivityDetailed(int userId, MyLearningRequestModel requestModel); + + /// + /// Gets the played segment data for the progress modal in My Learning screen. + /// + /// /// The user id. + /// The resourceId. + /// The majorVersion. + /// The . + Task> GetPlayedSegments(int userId, int resourceId, int majorVersion); + + /// + /// Gets the resource certificate details of a resource reference. + /// + /// /// The user id. + /// The resourceReferenceId. + /// The majorVersion. + /// The minorVersion. + /// The . + Task> GetResourceCertificateDetails(int userId, int resourceReferenceId, int majorVersion, int minorVersion); + + /// + /// Populate MyLearning Detailed ItemViewModels. + /// + /// The resource activities. + /// The user id. + /// The . + Task> PopulateMyLearningDetailedItemViewModels(List resourceActivities, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IPageService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IPageService.cs new file mode 100644 index 000000000..753a3fdfe --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IPageService.cs @@ -0,0 +1,175 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Content; + using LearningHub.Nhs.Models.Validation; + + /// + /// IDashboardService. + /// + public interface IPageService + { + /// + /// GetPagesAsync. + /// + /// A representing the result of the asynchronous operation. + Task GetPagesAsync(); + + /// + /// The GetPageById. + /// + /// The id. + /// includeHidden. + /// The published only. + /// Preview mode. + /// The . + Task GetPageByIdAsync(int id, bool includeHidden = false, bool publishedOnly = false, bool preview = false); + + /// + /// The GetPageSectionById. + /// + /// The page section id. + /// The . + Task GetPageSectionByIdAsync(int id); + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + Task GetPageSectionDetailImageAssetByIdAsync(int id); + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The userid. + /// The . + Task GetEditablePageSectionDetailByPageSectionIdAsync(int pageSectionDetailId, int currentUserId); + + /// + /// The GetPageSectionDetailVideoAsset. + /// + /// The id. + /// The . + Task GetPageSectionDetailVideoAssetByIdAsync(int id); + + /// + /// The DiscardAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + Task DiscardAsync(int pageId, int currentUserId); + + /// + /// The PublishAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + Task PublishAsync(int pageId, int currentUserId); + + /// + /// Update page image section detail. + /// + /// The pageId. + /// The update model. + /// currentUserId. + /// The . + Task UpdatePageImageSectionDetailAsync(int pageId, PageImageSectionUpdateViewModel model, int currentUserId); + + /// + /// The CloneAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + Task CloneAsync(int pageSectionId, int currentUserId); + + /// + /// The HideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task HideAsync(int pageSectionId, int currentUserId); + + /// + /// The UnHideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task UnHideAsync(int pageSectionId, int currentUserId); + + /// + /// The DeleteAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + Task DeleteAsync(int pageSectionId, int currentUserId); + + /// + /// The ChangeOrderAsync. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + Task ChangeOrderAsync(UpdatePageSectionOrderModel requestViewModel, int currentUserId); + + /// + /// The CreatePageSectionAsync. + /// + /// The requestViewModel. + /// The currentUserId. + /// The page section id. + Task CreatePageSectionAsync(PageSectionViewModel requestViewModel, int currentUserId); + + /// + /// The SaveVideoAssetAsync. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + Task SaveVideoAssetAsync(FileCreateRequestViewModel requestViewModel, int currentUserId); + + /// + /// The UpdateVideoAssetManifestDetailsAsync. + /// + /// The viewModel. + /// The . + Task UpdateVideoAssetManifestDetailsAsync(UpdateVideoAssetManifestRequestViewModel viewModel); + + /// + /// The UpdateVideoAssetAsync. + /// + /// The viewModel. + /// The . + Task UpdateVideoAssetAsync(VideoAssetViewModel viewModel); + + /// + /// The SaveAttributeFileDetails. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + Task SaveAttributeFileDetails(FileCreateRequestViewModel requestViewModel, int currentUserId); + + /// + /// The UpdateVideoAssetStateAsync. + /// + /// videoAssetStateViewModel. + /// A representing the result of the asynchronous operation. + Task UpdateVideoAssetStateAsync(UpdateVideoAssetStateViewModel videoAssetStateViewModel); + + /// + /// The UpdatePageSectionDetailsAsync. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + Task UpdatePageSectionDetailsAsync(PageSectionDetailViewModel requestViewModel, int currentUserId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceReferenceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceReferenceService.cs new file mode 100644 index 000000000..9e115bffa --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceReferenceService.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The Resource Reference Service interface. + /// + public interface IResourceReferenceService + { + /// + /// The get resource reference by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index a2628f478..2b4921579 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -2,15 +2,19 @@ namespace LearningHub.Nhs.OpenApi.Services.Interface.Services { using System.Collections.Generic; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Resource.Admin; using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.Models.Resource.ResourceDisplay; using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.OpenApi.Models.ViewModels; - using FileChunkDetailViewModel = Nhs.Models.Resource.Contribute.FileChunkDetailViewModel; - using FileCreateRequestViewModel = Nhs.Models.Resource.Contribute.FileCreateRequestViewModel; - using FileDeleteRequestModel = Nhs.Models.Resource.Contribute.FileDeleteRequestModel; + using FileChunkDetailViewModel = LearningHub.Nhs.Models.Resource.Contribute.FileChunkDetailViewModel; + using FileCreateRequestViewModel = LearningHub.Nhs.Models.Resource.Contribute.FileCreateRequestViewModel; + using FileDeleteRequestModel = LearningHub.Nhs.Models.Resource.Contribute.FileDeleteRequestModel; /// /// The ResourceService interface. @@ -50,6 +54,14 @@ public interface IResourceService /// The . Task GetResourceVersionByResourceReferenceAsync(int resourceReferenceId); + /// + /// Get file directory for unpublished or deleted versions. + /// + /// The resourceVersionId. + /// . + /// The . + Task> GetObsoleteResourceFile(int resourceVersionId, bool deletedResource = false); + /// /// The unpublish resource version. /// @@ -264,6 +276,14 @@ Task GetAssessmentProgress( /// The . Task GetCaseDetailsByIdAsync(int resourceVersionId); + /// + /// The get case resource version async. + /// + /// The resource version id. + /// The resource type. + /// The . + Task> GetResourceBlockCollectionsFilePathAsync(int excludeResourceVersionId, ResourceTypeEnum resourceType); + /// /// The GetFileStatusDetailsAsync. /// @@ -341,6 +361,12 @@ Task GetAssessmentProgress( /// The . Task CreateNewResourceVersionAsync(int resourceId, int currentUserId); + /// + /// The create resource version validation result async. + /// + /// The validationResultViewModel. + /// The . + Task CreateResourceVersionValidationResultAsync(ResourceVersionValidationResultViewModel validationResultViewModel); /// /// The set resource type. @@ -382,6 +408,7 @@ Task GetAssessmentProgress( /// The . Task DeleteResourceVersionAsync(int resourceVersionId, int userId); + /// /// The update generic file detail async. /// @@ -505,6 +532,22 @@ Task SaveResourceVersionFlagAsync( /// The . Task DeleteResourceVersionFlagAsync(int resourceVersionFlagId, int userId); + /// + /// Returns a list of basic resource info - filtered, sorted and paged as required. + /// + /// The userId. + /// The request detail . + /// The . + Task> GetResourceAdminSearchFilteredPageAsync(int currentUserId, PagingRequestModel pagingRequestModel); + + /// + /// Transfer Resource Ownership. + /// + /// The transferResourceOwnershipViewModel. + /// The userId. + /// The . + Task TransferResourceOwnership(TransferResourceOwnershipViewModel transferResourceOwnershipViewModel, int userId); + /// /// The update web link resource version async. /// @@ -562,5 +605,48 @@ Task SaveResourceVersionFlagAsync( /// The . Task CreateFileDetailsAsync(FileCreateRequestViewModel fileCreateRequestViewModel, int userId); + /// + /// The get resource version events async. + /// + /// The resourceVersionId. + /// The . + Task> GetResourceVersionEventsAsync(int resourceVersionId); + + /// + /// Create resource version event. + /// + /// The resourceVersionEventViewModel. + void CreateResourceVersionEvent(ResourceVersionEventViewModel resourceVersionEventViewModel); + + /// + /// The get extended resource version view model async. + /// + /// The resourceVersionId. + /// The userId. + /// The . + Task GetResourceVersionExtendedViewModelAsync(int resourceVersionId, int userId); + + /// + /// The GetResourceVersionDevIdDetailsAync. + /// + /// The resourceVersionId. + /// The . + Task GetResourceVersionDevIdDetailsAync(int resourceVersionId); + + /// + /// The get resource version dev id async. + /// + /// The devId. + /// The . + Task DoesDevIdExistsAync(string devId); + + /// + /// Update Dev Id details. + /// + /// The resourceVersionDevIdViewModel. + /// The currentUserId. + /// The . + Task UpdateDevIdDetailsAsync(ResourceVersionDevIdViewModel resourceVersionDevIdViewModel, int currentUserId); + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs index f881187d9..194264b15 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs @@ -2,6 +2,8 @@ namespace LearningHub.Nhs.OpenApi.Services.Interface.Services { using System.Threading.Tasks; using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Search.SearchClick; + using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.OpenApi.Models.ServiceModels.Resource; using LearningHub.Nhs.OpenApi.Models.ViewModels; @@ -10,6 +12,117 @@ namespace LearningHub.Nhs.OpenApi.Services.Interface.Services /// public interface ISearchService { + /// + /// The get search result async. + /// + /// The catalog search request model. + /// The user id. + /// The . + Task GetSearchResultAsync(SearchRequestModel searchRequestModel, int userId); + + /// + /// The Get Catalogue Search Result Async method. + /// + /// + /// The catalog search request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + Task GetCatalogueSearchResultAsync(CatalogueSearchRequestModel catalogSearchRequestModel, int userId); + + /// + /// The create resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + Task CreateResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId); + + /// + /// The create catalogue search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + Task CreateCatalogueSearchActionAsync(SearchActionCatalogueModel searchActionCatalogueModel, int userId); + + /// + /// The create catalogue resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + Task CreateCatalogueResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId); + + /// + /// The submit feedback async. + /// + /// The search feedback. + /// The user id. + /// The . + Task SubmitFeedbackAsync(SearchFeedBackModel searchFeedbackModel, int userId); + + + /// + /// create search term event. + /// + /// search request. + /// user id. + /// The . + Task CreateSearchTermEvent(SearchRequestModel searchRequestModel, int userId); + + /// + /// Create catalogue search term event. + /// + /// catalogue search request model. + /// user id. + /// The . + Task CreateCatalogueSearchTermEvent(CatalogueSearchRequestModel catalogueSearchRequestModel, int userId); + + /// + /// The create resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The . + /// + Task SendResourceSearchEventClickAsync(SearchActionResourceModel searchActionResourceModel); + + /// + /// Create catalogue search term. + /// + /// catalogue search request model. + /// results per page. + /// user id. + /// + /// The . + /// + Task CreateCatalogueSearchTerm(CatalogueSearchRequestModel catalogueSearchRequestModel, int resultsPerPage, int userId); + /// /// No info. /// @@ -17,6 +130,17 @@ public interface ISearchService /// . Task Search(ResourceSearchRequest query, int? currentUserId); + /// + /// The create catalogue search action async. + /// + /// + /// The search action request model. + /// + /// + /// The . + /// + Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchActionCatalogueModel); + /// /// Gets AllCatalogue search results async. /// @@ -31,6 +155,13 @@ public interface ISearchService /// The . Task GetAutoSuggestionResultsAsync(string term); + /// + /// The Send AutoSuggestion Event Async. + /// + /// The click Payload Model. + /// The . + Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadModel clickPayloadModel); + /// /// The remove resource from search method. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs new file mode 100644 index 000000000..58ff59cd7 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs @@ -0,0 +1,160 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + + /// + /// The UserGroupService interface. + /// + public interface IUserGroupService + { + /// + /// The get by id async. + /// + /// The id. + /// The include roles. + /// The . + Task GetByIdAsync(int id, bool includeRoles); + + /// + /// The create async. + /// + /// The user id. + /// The user group. + /// The . + Task CreateAsync(int userId, UserGroup userGroup); + + /// + /// The delete async. + /// + /// The user id. + /// The user group id. + /// The . + Task DeleteAsync(int userId, int userGroupId); + + /// + /// Returns a user group detail view model for the supplied id. + /// + /// The user group id. + /// The . + Task GetUserGroupAdminDetailByIdAsync(int id); + + /// + /// Returns a list of role user group detail view models for the supplied user group id. + /// + /// The user group id. + /// The list of . + Task> GetUserGroupRoleDetailByUserGroupId(int userGroupId); + + /// + /// Returns a list of role user group detail view models for the supplied user id. + /// + /// The user group id. + /// The list of . + Task> GetRoleUserGroupDetailByUserId(int userId); + + /// + /// Create a user group. + /// + /// The user group admin detail view model. + /// The current user id. + /// The . + Task CreateUserGroupAsync(UserGroupAdminDetailViewModel userGroupAdminDetailViewModel, int currentUserId); + + /// + /// Updates a user group. + /// + /// The user group admin detail view model. + /// The current user id. + /// The . + Task UpdateUserGroupAsync(UserGroupAdminDetailViewModel userGroupAdminDetailViewModel, int currentUserId); + + /// + /// Returns a list of "role - user group" info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + Task> GetRoleUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); + + /// + /// Returns a list of "user - user group" info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + Task> GetUserUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); + + /// + /// Returns a list of basic user group info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The preset filter. + /// The filter. + /// The . + Task> GetUserGroupAdminBasicPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); + + /// + /// Adds a list of users to a user group. + /// + /// The user user group view model list. + /// The current user id. + /// The . + Task AddUserUserGroups(List userUserGroups, int currentUserId); + + /// + /// Removes user from a user group. + /// + /// The user user group view model. + /// The current user id. + /// The . + Task DeleteUserUserGroupAsync(UserUserGroupViewModel userUserGroupViewModel, int currentUserId); + + /// + /// Removes a role - user group. + /// + /// The role user group view model. + /// The current user id. + /// The . + Task DeleteRoleUserGroupAsync(RoleUserGroupUpdateViewModel roleUserGroupViewModel, int currentUserId); + + /// + /// Adds a list of role user groups. + /// + /// The role user group view model list. + /// The current user id. + /// The . + Task AddRoleUserGroups(List roleUserGroups, int currentUserId); + + /// + /// Adds a user group attribute. + /// + /// The user group attribute view model. + /// The current user id. + /// The . + Task AddUserGroupAttribute(UserGroupAttributeViewModel userGroupAttribute, int currentUserId); + + /// + /// Removes a user group attribute. + /// + /// The user group attribute view model. + /// The current user id. + /// The . + Task DeleteUserGroupAttributeAsync(UserGroupAttributeViewModel userGroupAttribute, int currentUserId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserLearningRecordService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserLearningRecordService.cs new file mode 100644 index 000000000..4c0a69e27 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserLearningRecordService.cs @@ -0,0 +1,24 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.MyLearning; + + /// + /// The MyLearningService interface. + /// + public interface IUserLearningRecordService + { + /// + /// GetUserLearningRecordsAsync. + /// + /// page. + /// pageSize. + /// sortColumn. + /// sortDirection. + /// presetFilter. + /// filter. + /// The . + Task> GetUserLearningRecordsAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserProviderService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserProviderService.cs new file mode 100644 index 000000000..1a7c402fa --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserProviderService.cs @@ -0,0 +1,19 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Provider; + using LearningHub.Nhs.Models.Validation; + + /// + /// The User Provider Service interface. + /// + public interface IUserProviderService + { + /// + /// The update provider list by user id async. + /// + /// The user provider update model. + /// The . + Task UpdateUserProviderAsync(UserProviderUpdateViewModel userProviderUpdateModel); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Extensions/PageExtension.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Extensions/PageExtension.cs new file mode 100644 index 000000000..00808491a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Extensions/PageExtension.cs @@ -0,0 +1,92 @@ +namespace LearningHub.Nhs.OpenApi.Services.Extensions +{ + using System.Collections.Generic; + using System.Linq; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Enums.Content; + + /// + /// Defines the . + /// + public static class PageExtension + { + /// + /// The CanPublish. + /// + /// The pageModel. + /// The . + public static bool CanPublish(this Page pageModel) + { + var excludedPublishStatusColl = new List { PageSectionStatus.Processing, PageSectionStatus.ProcessingFailed }; + var pageSections = pageModel.PageSections.Where(ps => + { + var psd = ps.PageSectionDetails.First(); + return !ps.IsHidden || psd.DraftHidden.HasValue && !psd.DraftHidden.Value; + }); + var canPublish = pageSections.All(ps => !excludedPublishStatusColl.Contains((PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId)); + if (canPublish) + { + canPublish = !pageSections.All(ps => (PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId == PageSectionStatus.Live); + } + + return pageModel.PageSections.Count() > 0 && canPublish; + } + + /// + /// The CanPreview. + /// + /// The pageModel. + /// The . + public static bool CanPreview(this Page pageModel) + { + var canPreviewStatusCol = new List { PageSectionStatus.Draft, PageSectionStatus.Processed }; + var dissallowPreviewStatusCol = new List { PageSectionStatus.Processing, PageSectionStatus.ProcessingFailed }; + var pageSections = pageModel.PageSections.Where(ps => + { + var psd = ps.PageSectionDetails.First(); + return !ps.IsHidden || psd.DraftHidden.HasValue && !psd.DraftHidden.Value; + }); + return pageSections.Count() > 0 + && !pageSections.All(ps => dissallowPreviewStatusCol.Contains((PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId)) + && pageSections.Any(ps => canPreviewStatusCol.Contains((PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId)); + } + + /// + /// The CanDiscard. + /// + /// The pageModel. + /// The . + public static bool CanDiscard(this Page pageModel) + { + var allowedDiscardStatusColl = new List { PageSectionStatus.Draft, PageSectionStatus.Processing, PageSectionStatus.ProcessingFailed, PageSectionStatus.Processed }; + var pageSections = pageModel.PageSections.Where(ps => ps.IsHidden == false); + return pageSections.Count() > 0 && pageModel.PageSections.Any(ps => allowedDiscardStatusColl.Contains((PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId)); + } + + /// + /// The GetStatus. + /// + /// The pageModel. + /// The . + public static PageStatus GetStatus(this Page pageModel) + { + return pageModel.PageSections.Count() > 0 && pageModel.PageSections.Any(ps => (PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId != PageSectionStatus.Live) ? PageStatus.EditsPending : PageStatus.Live; + } + + /// + /// The HasHiddenSections. + /// + /// The pageModel. + /// The . + public static bool HasHiddenSections(this Page pageModel) + { + return pageModel.PageSections.Count() > 0 + && (pageModel.PageSections.Any(ps => ps.IsHidden) + || pageModel.PageSections.Any(ps => + { + var dh = ps.PageSectionDetails.OrderBy(x => x.Id).First().DraftHidden; + return dh.HasValue && dh.Value; + })); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/BinaryFormatterHelper.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/BinaryFormatterHelper.cs new file mode 100644 index 000000000..3262cfd2b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/BinaryFormatterHelper.cs @@ -0,0 +1,21 @@ +namespace LearningHub.Nhs.OpenApi.Services.Helpers +{ + /// + /// The querystring helper. + /// + public class BinaryFormatterHelper + { + /// + /// Encode to base64. + /// + /// json string. + /// + /// The . + /// + public static string Base64EncodeObject(string json) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(json); + return System.Convert.ToBase64String(plainTextBytes); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/BookmarkService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/BookmarkService.cs index 289d56bd1..92756960c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/BookmarkService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/BookmarkService.cs @@ -9,6 +9,7 @@ using LearningHub.Nhs.Models.Entities; using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; using LearningHub.Nhs.OpenApi.Services.HttpClients; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using Microsoft.EntityFrameworkCore; @@ -23,6 +24,7 @@ public class BookmarkService : IBookmarkService private readonly IOptions learningHubApiConfig; private readonly IMapper mapper; private readonly IBookmarkRepository bookmarkRepository; + private readonly IResourceReferenceRepository resourceReferenceRepository; /// /// Initializes a new instance of the class. @@ -30,10 +32,11 @@ public class BookmarkService : IBookmarkService /// Learning Hub Api config details. /// The mapper. /// The bookmarkRepository. - public BookmarkService(IOptions learningHubApiConfig, IMapper mapper, IBookmarkRepository bookmarkRepository) + public BookmarkService(IOptions learningHubApiConfig, IMapper mapper, IBookmarkRepository bookmarkRepository, IResourceReferenceRepository resourceReferenceRepository) { this.mapper = mapper; this.bookmarkRepository = bookmarkRepository; + this.resourceReferenceRepository = resourceReferenceRepository; this.learningHubApiConfig = learningHubApiConfig; } @@ -54,6 +57,29 @@ public async Task> GetAllByParent(string auth response.Content.ReadAsStringAsync().Result); } + /// + public async Task> GetAllByParent(int currentUserId, int? parentId, bool? all = false) + { + var userBookmarks = this.bookmarkRepository.GetAll().Where(ub => ub.UserId == currentUserId && ub.Deleted == false).OrderBy(ub => ub.Position).ThenBy(ub => ub.AmendDate); + + var bookmarks = all == true ? userBookmarks : userBookmarks.Where(ub => ub.ParentId == parentId); + + var userBookmarkResult = this.mapper.Map>(bookmarks); + + foreach (var b in userBookmarkResult.Where(ub => ub.BookmarkTypeId == 1)) + { + b.ChildrenCount = userBookmarks.Where(ub => ub.ParentId == b.Id).Count(); + } + + foreach (var b in userBookmarkResult.Where(ub => ub.ResourceReferenceId.HasValue)) + { + var resourceRef = await this.resourceReferenceRepository.GetByOriginalResourceReferenceIdAsync(b.ResourceReferenceId.Value, false); + b.ResourceTypeId = (int?)resourceRef.Resource.ResourceTypeEnum; + } + + return userBookmarkResult; + } + public async Task Create(int currentUserId, UserBookmarkViewModel bookmarkViewModel) { var bookmarkModel = this.mapper.Map(bookmarkViewModel); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 158f94e23..ab0592181 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -109,6 +109,17 @@ public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepositor return new Models.ViewModels.BulkCatalogueViewModel(catalogueViewModels); } + /// + /// The Get Basic Catalogue. + /// + /// The catalogueNode. + /// The catalogues. + public CatalogueBasicViewModel GetBasicCatalogue(int catalogueNodeId) + { + var catalogue = this.catalogueNodeVersionRepository.GetBasicCatalogue(catalogueNodeId); + return this.mapper.Map(catalogue); + } + /// /// The GetCatalogue. @@ -402,6 +413,24 @@ public async Task CanUserEditCatalogueAsync(int userId, int catalogueId) return false; } + /// + /// The GetCatalogues. + /// + /// The catalogue ids. + /// The catalogues. + public async Task> GetCatalogues(List catalogueIds) + { + var catalogueViewModels = new List(); + + if (catalogueIds.Count > 0) + { + var catalogueNodeVersions = await this.catalogueNodeVersionRepository.GetCatalogues(catalogueIds); + catalogueViewModels = this.mapper.Map>(catalogueNodeVersions); + } + + return catalogueViewModels; + } + /// /// The Get Basic Catalogues for user. /// @@ -769,18 +798,6 @@ public async Task ValidateForRestrictedAccess(int c return retVal; } - /// - /// The Get Basic Catalogue. - /// - /// The catalogueNode. - /// The catalogues. - public CatalogueBasicViewModel GetBasicCatalogue(int catalogueNodeId) - { - var catalogue = this.catalogueNodeVersionRepository.GetBasicCatalogue(catalogueNodeId); - return this.mapper.Map(catalogue); - } - - /// /// The HideCatalogueAsync. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs new file mode 100644 index 000000000..dfc2bfde2 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -0,0 +1,140 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + + /// + /// The DashboardService. + /// + public class DashboardService : IDashboardService + { + private readonly IMapper mapper; + private IResourceVersionRepository resourceVersionRepository; + private ICatalogueNodeVersionRepository catalogueNodeVersionRepository; + private IRatingService ratingService; + private IProviderService providerService; + + /// + /// Initializes a new instance of the class. + /// + /// mapper. + /// resourceVersionRepository. + /// catalogueNodeVersionRepository. + /// ratingService. + /// providerService. + public DashboardService(IMapper mapper, IResourceVersionRepository resourceVersionRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, IRatingService ratingService, IProviderService providerService) + { + this.mapper = mapper; + this.resourceVersionRepository = resourceVersionRepository; + this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; + this.ratingService = ratingService; + this.providerService = providerService; + } + + /// + /// GetMyAccessLearnings. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// A representing the result of the asynchronous operation. + public async Task GetMyAccessLearnings(string dashboardType, int pageNumber, int userId) + { + var (resourceCount, resources) = dashboardType.ToLower() != "my-catalogues" ? resourceVersionRepository.GetResources(dashboardType, pageNumber, userId) : (resourceCount: 0, resources: new List()); + + var cataloguesResponse = dashboardType.ToLower() == "my-catalogues" ? catalogueNodeVersionRepository.GetCatalogues(dashboardType, pageNumber, userId) : (TotalCount: 0, Catalogues: new List()); + + var catalogueList = cataloguesResponse.Catalogues.Any() ? mapper.Map>(cataloguesResponse.Catalogues) : new List(); + if (catalogueList.Any()) + { + foreach (var catalogue in catalogueList) + { + catalogue.Providers = await providerService.GetByCatalogueVersionIdAsync(catalogue.NodeVersionId); + } + } + + var resourceList = resources.Any() ? mapper.Map>(resources) : new List(); + if (resourceList.Any()) + { + foreach (var resource in resourceList) + { + resource.Providers = await providerService.GetByResourceVersionIdAsync(resource.ResourceVersionId); + } + } + + var response = new DashboardMyLearningResponseViewModel + { + Type = dashboardType, + Resources = resourceList, + Catalogues = catalogueList, + TotalCount = dashboardType.ToLower() == "my-catalogues" ? cataloguesResponse.TotalCount : resourceCount, + CurrentPage = pageNumber, + }; + + return response; + } + + /// + /// GetCatalogues. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// A representing the result of the asynchronous operation. + public async Task GetCatalogues(string dashboardType, int pageNumber, int userId) + { + var (catalogueCount, catalogues) = catalogueNodeVersionRepository.GetCatalogues(dashboardType, pageNumber, userId); + + var catalogueList = catalogues.Any() ? mapper.Map>(catalogues) : new List(); + foreach (var catalogue in catalogueList) + { + catalogue.Providers = await providerService.GetByCatalogueVersionIdAsync(catalogue.NodeVersionId); + } + + var response = new DashboardCatalogueResponseViewModel + { + Type = dashboardType, + Catalogues = catalogueList, + TotalCount = catalogueCount, + CurrentPage = pageNumber, + }; + return response; + } + + /// + /// GetResources. + /// + /// The dashboard type. + /// The number of rows to return. + /// The userId. + /// A representing the result of the asynchronous operation. + public async Task GetResources(string dashboardType, int pageNumber, int userId) + { + var (resourceCount, resources) = resourceVersionRepository.GetResources(dashboardType, pageNumber, userId); + + var resourceList = resources.Any() ? mapper.Map>(resources) : new List(); + + foreach (var resource in resourceList) + { + resource.Providers = await providerService.GetByResourceVersionIdAsync(resource.ResourceVersionId); + } + + var response = new DashboardResourceResponseViewModel + { + Type = dashboardType, + Resources = resourceList, + TotalCount = resourceCount, + CurrentPage = pageNumber, + }; + + return response; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventLogService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventLogService.cs new file mode 100644 index 000000000..c58f1f83e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventLogService.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using LearningHub.Nhs.Models.EventLog; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Logging; + + /// + /// The event log service. + /// + public class EventLogService : IEventLogService + { + /// + /// The logger. + /// + private ILogger logger; + + /// + /// The event log repository. + /// + private IEventLogRepository eventLogRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The role repository. + /// The logger. + public EventLogService( + IEventLogRepository eventLogRepository, + ILogger logger) + { + this.eventLogRepository = eventLogRepository; + this.logger = logger; + } + + /// + /// Create event. + /// + /// eventViewModel. + public void CreateEvent(EventViewModel eventViewModel) + { + eventLogRepository.CreateEvent(eventViewModel.EventLog, eventViewModel.EventType, eventViewModel.HierarchyEditId, eventViewModel.NodeId, eventViewModel.ResourceVersionId, eventViewModel.Details, eventViewModel.CreateUserId); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventService.cs new file mode 100644 index 000000000..b2f22f08a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/EventService.cs @@ -0,0 +1,99 @@ +namespace LearningHub.Nhs.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Analytics; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Analytics; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Logging; + + /// + /// The event service. + /// + public class EventService : IEventService + { + /// + /// The logger. + /// + private ILogger logger; + + /// + /// The event repository. + /// + private IEventRepository eventRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The role repository. + /// The logger. + public EventService( + IEventRepository eventRepository, + ILogger logger) + { + this.eventRepository = eventRepository; + this.logger = logger; + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await this.eventRepository.GetByIdAsync(id); + } + + /// + /// The create async. + /// + /// The user id. + /// The event. + /// The . + public async Task CreateAsync(int userId, Event eventEntity) + { + var retVal = await this.ValidateAsync(eventEntity); + + if (retVal.IsValid) + { + retVal.CreatedId = await this.eventRepository.CreateAsync(userId, eventEntity); + } + + return retVal; + } + + /// + /// The update async. + /// + /// The user id. + /// The event. + /// The . + public async Task UpdateAsync(int userId, Event eventEntity) + { + var retVal = await this.ValidateAsync(eventEntity); + + if (retVal.IsValid) + { + await this.eventRepository.UpdateAsync(userId, eventEntity); + } + + return retVal; + } + + /// + /// The validate async. + /// + /// The event. + /// The . + public async Task ValidateAsync(Event eventEntity) + { + var eventValidator = new EventValidator(); + var clientValidationResult = await eventValidator.ValidateAsync(eventEntity); + + var retVal = new LearningHubValidationResult(clientValidationResult); + + return retVal; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs new file mode 100644 index 000000000..343626909 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -0,0 +1,507 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Repositories.Helpers; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Options; + + /// + /// The rating service. + /// + public class MyLearningService : IMyLearningService + { + /// + /// The resourceActivityRepository. + /// + private readonly IResourceActivityRepository resourceActivityRepository; + private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; + + /// + /// The mediaResourcePlayedSegmentRepository. + /// + private readonly IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository; + + /// + /// The assessment resource activity repository. + /// + private readonly IAssessmentResourceActivityRepository assessmentResourceActivityRepository; + + /// + /// The assessment resource activity interaction repository. + /// + private readonly IAssessmentResourceActivityInteractionRepository assessmentResourceActivityInteractionRepository; + + /// + /// The scorm resource activity interaction repository. + /// + private readonly IScormActivityRepository scormActivityRepository; + + /// + /// The media resource activity interaction repository. + /// + private readonly IMediaResourceActivityRepository mediaResourceActivity; + + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// The settings. + /// + private readonly LearningHubConfig settings; + + /// + /// Initializes a new instance of the class. + /// + /// The resource activity repository. + /// The catalogue node repository. + /// The mediaResourcePlayedSegmentRepository. + /// The assessmentResourceActivityRepository. + /// The assessmentResourceActivityInteractionRepository. + /// The mapper. + /// The settings. + /// The scormActivityRepository. + /// The mediaResourceActivity. + public MyLearningService( + IResourceActivityRepository resourceActivityRepository, + IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository, + IAssessmentResourceActivityRepository assessmentResourceActivityRepository, + IAssessmentResourceActivityInteractionRepository assessmentResourceActivityInteractionRepository, + ICatalogueNodeVersionRepository catalogueNodeVersionRepository, + IMapper mapper, + IOptions settings, + IScormActivityRepository scormActivityRepository, + IMediaResourceActivityRepository mediaResourceActivity) + { + this.resourceActivityRepository = resourceActivityRepository; + this.mediaResourcePlayedSegmentRepository = mediaResourcePlayedSegmentRepository; + this.assessmentResourceActivityRepository = assessmentResourceActivityRepository; + this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; + this.assessmentResourceActivityInteractionRepository = assessmentResourceActivityInteractionRepository; + this.mapper = mapper; + this.settings = settings.Value; + this.scormActivityRepository = scormActivityRepository; + this.mediaResourceActivity = mediaResourceActivity; + } + + /// + /// Gets the activity records for the detailed activity tab of My Learning screen. + /// + /// /// The user id. + /// The request model. + /// The . + public async Task GetActivityDetailed(int userId, MyLearningRequestModel requestModel) + { + var activityQuery = resourceActivityRepository.GetByUserIdFromSP(userId, requestModel, settings.DetailedMediaActivityRecordingStartDate).Result.OrderByDescending(r => r.ActivityStart).DistinctBy(l => l.Id); + + // Count total records. + MyLearningDetailedViewModel viewModel = new MyLearningDetailedViewModel() + { + TotalCount = resourceActivityRepository.GetTotalCount(userId, requestModel, settings.DetailedMediaActivityRecordingStartDate), + }; + + // Return only the requested batch. + var activityEntities = activityQuery.ToList(); + + viewModel.Activities = await this.PopulateMyLearningDetailedItemViewModels(activityEntities, userId); + + return viewModel; + } + + /// + /// Gets the played segment data for the progress modal in My Learning screen. + /// + /// /// The user id. + /// The resourceId. + /// The majorVersion. + /// The . + public async Task> GetPlayedSegments(int userId, int resourceId, int majorVersion) + { + var playedSegments = await mediaResourcePlayedSegmentRepository.GetPlayedSegmentsAsync(userId, resourceId, majorVersion); + var viewModel = mapper.Map>(playedSegments); + + return viewModel; + } + + /// + /// Gets the resource certificate details of a resource reference. + /// + /// /// The user id. + /// The resourceReferenceId. + /// The majorVersion. + /// The minorVersion. + /// The . + public async Task> GetResourceCertificateDetails(int userId, int resourceReferenceId, int majorVersion, int minorVersion) + { + MyLearningDetailedItemViewModel myLearningDetailedItemViewModel = null; + var activityQuery = resourceActivityRepository.GetByUserId(userId); + if (majorVersion > 0) + { + activityQuery = activityQuery.Where(x => x.MajorVersion == majorVersion && x.MinorVersion == minorVersion); + } + else + { + // filter and use the latest version only + activityQuery = activityQuery.Where(rv => rv.ResourceVersionId == rv.Resource.CurrentResourceVersionId); + } + + activityQuery = activityQuery.Where(x => x.Resource.ResourceReference.FirstOrDefault() != null && x.Resource.ResourceReference.Any(rr => rr.OriginalResourceReferenceId == resourceReferenceId)); + int totalNumberOfAccess = activityQuery.Count(); + var activityEntities = await activityQuery.OrderByDescending(x => x.Score).ThenByDescending(x => x.ActivityStart).ToListAsync(); + activityEntities.RemoveAll(x => x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm && (x.ActivityStatusId == (int)ActivityStatusEnum.Downloaded || x.ActivityStatusId == (int)ActivityStatusEnum.Incomplete || x.ActivityStatusId == (int)ActivityStatusEnum.InProgress)); + if (activityEntities.Any() && activityEntities.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment) + { + totalNumberOfAccess = activityQuery.SelectMany(x => x.AssessmentResourceActivity).OrderByDescending(a => a.CreateDate).ToList().Count(); + activityEntities = activityEntities.Where(x => x.AssessmentResourceActivity.FirstOrDefault() != null && x.AssessmentResourceActivity.FirstOrDefault().Score.HasValue && (int)Math.Round(x.AssessmentResourceActivity.FirstOrDefault().Score.Value, MidpointRounding.AwayFromZero) >= x.ResourceVersion.AssessmentResourceVersion.PassMark).ToList(); + } + else if (activityEntities.Any() && (activityEntities.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Video || activityEntities.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio)) + { + totalNumberOfAccess = activityQuery.SelectMany(x => x.MediaResourceActivity).OrderByDescending(a => a.CreateDate).ToList().Count(); + } + + if (activityEntities.Any()) + { + ResourceActivity filteredActivity = null; + if (activityEntities.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || activityEntities.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) + { + filteredActivity = activityEntities.Where(x => x.MediaResourceActivity != null && x.MediaResourceActivity.Any(x => x.PercentComplete == 100)).OrderByDescending(x => x.ActivityStart).FirstOrDefault(); + } + else + { + filteredActivity = activityEntities.Where(x => x.ActivityStatusId == (int)ActivityStatusEnum.Completed || x.ActivityStatusId == (int)ActivityStatusEnum.Incomplete || x.ActivityStatusId == (int)ActivityStatusEnum.Passed || x.ActivityStatusId == (int)ActivityStatusEnum.Downloaded).OrderByDescending(x => x.ActivityStart).FirstOrDefault(); + } + + if (filteredActivity != null) + { + var result = await PopulateMyLearningDetailedItemViewModels(new List { filteredActivity }, userId); + myLearningDetailedItemViewModel = result.FirstOrDefault(); + if (filteredActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm) + { + var sa = scormActivityRepository.GetAll().OrderByDescending(x => x.Id).Where(x => x.ResourceActivity.ResourceVersionId == filteredActivity.ResourceVersionId && x.CreateUserId == filteredActivity.UserId); + if (sa.Any()) + { + myLearningDetailedItemViewModel.ActivityDurationSeconds = 0; + foreach (var item in sa.Where(x => x.CmiCoreLessonStatus.HasValue)) + { + myLearningDetailedItemViewModel.ActivityDurationSeconds = myLearningDetailedItemViewModel.ActivityDurationSeconds + item.DurationSeconds; + } + } + } + else if (filteredActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Video || filteredActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio) + { + var ma = mediaResourceActivity.GetAll().OrderByDescending(x => x.Id).Where(x => x.ResourceActivity.ResourceVersionId == filteredActivity.ResourceVersionId && x.CreateUserId == filteredActivity.UserId); + if (ma.Any()) + { + myLearningDetailedItemViewModel.ActivityDurationSeconds = 0; + foreach (var item in ma) + { + if (item.SecondsPlayed.HasValue) + { + myLearningDetailedItemViewModel.ActivityDurationSeconds = myLearningDetailedItemViewModel.ActivityDurationSeconds + (int)item.SecondsPlayed; + } + } + } + } + + return new Tuple(totalNumberOfAccess, result.FirstOrDefault()); + } + + return new Tuple(0, myLearningDetailedItemViewModel); + } + + return new Tuple(0, myLearningDetailedItemViewModel); + } + + /// + public async Task> PopulateMyLearningDetailedItemViewModels(List resourceActivities, int userId) + { + var viewModels = new List(); + + var activityNodes = (from entity in resourceActivities + let data = entity.NodePath.CatalogueNodeId + select data).Distinct().ToArray(); + + var activityCatalogues = catalogueNodeVersionRepository.GetAll() + .Include(x => x.NodeVersion).Where(c => activityNodes.Contains(c.NodeVersion.NodeId)); + + foreach (ResourceActivity resourceActivity in resourceActivities) + { + var viewModel = new MyLearningDetailedItemViewModel() + { + Title = resourceActivity.ResourceVersion.Title, + ResourceId = resourceActivity.ResourceId, + MajorVersion = resourceActivity.MajorVersion, + MinorVersion = resourceActivity.MinorVersion, + Version = resourceActivity.MajorVersion + "." + resourceActivity.MinorVersion, + ResourceType = resourceActivity.Resource.ResourceTypeEnum, + ActivityDate = resourceActivity.ActivityStart.GetValueOrDefault(), + ActivityStatus = (ActivityStatusEnum)resourceActivity.ActivityStatusId, + IsCurrentResourceVersion = resourceActivity.ResourceVersionId == resourceActivity.Resource.CurrentResourceVersionId, + VersionStatusId = (int?)resourceActivity.ResourceVersion.VersionStatusEnum, + }; + + var latestActivityCheck = await resourceActivityRepository.GetAllTheActivitiesFromSP(resourceActivity.CreateUserId, resourceActivity.ResourceVersionId); + var allAttempts = latestActivityCheck; + latestActivityCheck.RemoveAll(x => x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm && (x.ActivityStatusId == (int)ActivityStatusEnum.Downloaded || x.ActivityStatusId == (int)ActivityStatusEnum.Incomplete || x.ActivityStatusId == (int)ActivityStatusEnum.InProgress)); + if (latestActivityCheck.Any() && latestActivityCheck.FirstOrDefault()?.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment) + { + latestActivityCheck = latestActivityCheck.Where(x => x.AssessmentResourceActivity.FirstOrDefault() != null && x.AssessmentResourceActivity.FirstOrDefault().Score.HasValue && (int)Math.Round(x.AssessmentResourceActivity.FirstOrDefault().Score.Value, MidpointRounding.AwayFromZero) >= x.ResourceVersion.AssessmentResourceVersion.PassMark).ToList(); + } + + ResourceActivity expectedActivity = null; + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) + { + expectedActivity = latestActivityCheck.Where(x => x.LaunchResourceActivityId == resourceActivity.Id && resourceActivity.MediaResourceActivity != null && resourceActivity.MediaResourceActivity.Any(y => y.PercentComplete == 100)).OrderByDescending(y => y.ActivityStart).FirstOrDefault(); + } + else + { + expectedActivity = latestActivityCheck.Where(x => x.Id == resourceActivity.Id && (x.ActivityStatusId == (int)ActivityStatusEnum.Completed || x.ActivityStatusId == (int)ActivityStatusEnum.Incomplete || x.ActivityStatusId == (int)ActivityStatusEnum.Passed || x.ActivityStatusId == (int)ActivityStatusEnum.Downloaded)).OrderByDescending(x => x.ActivityStart).FirstOrDefault(); + } + + if (latestActivityCheck.Any() && expectedActivity != null) + { + bool isExpectedActivityIdMatched = false; + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) + { + isExpectedActivityIdMatched = latestActivityCheck.OrderByDescending(x => x.ActivityStart).FirstOrDefault().Id == expectedActivity.LaunchResourceActivityId; + } + else + { + isExpectedActivityIdMatched = latestActivityCheck.OrderByDescending(x => x.ActivityStart).FirstOrDefault().Id == expectedActivity.Id; + } + + viewModel.CertificateEnabled = isExpectedActivityIdMatched && resourceActivity.ResourceVersion.CertificateEnabled.GetValueOrDefault(false); + } + else + { + viewModel.CertificateEnabled = false; + } + + var catalogueNodeVersion = await activityCatalogues + .SingleOrDefaultAsync(x => x.NodeVersion.NodeId == resourceActivity.NodePath.CatalogueNodeId); + if (catalogueNodeVersion != null) + { + viewModel.CertificateUrl = catalogueNodeVersion.CertificateUrl; + } + + var resourceReference = resourceActivity.Resource.ResourceReference.FirstOrDefault(x => x.NodePathId == resourceActivity.NodePathId); + + if (resourceReference != null) + { + viewModel.ResourceReferenceId = resourceReference.OriginalResourceReferenceId; + } + + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm) + { + viewModel.ScorePercentage = resourceActivity.Score.HasValue ? (int)resourceActivity.Score : 0; + viewModel.MasteryScore = resourceActivity.ResourceVersion.ScormResourceVersion.ScormResourceVersionManifest.MasteryScore; + var sa = resourceActivity.ScormActivity.FirstOrDefault(); + if (sa != null && sa.CmiCoreLessonStatus.HasValue) + { + viewModel.ActivityStatus = (ActivityStatusEnum)sa.CmiCoreLessonStatus; + viewModel.ActivityDurationSeconds = sa.DurationSeconds; + viewModel.ScorePercentage = sa.CmiCoreScoreRaw.HasValue ? (int)sa.CmiCoreScoreRaw : 0; + } + } + + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Video || resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio) + { + if (resourceActivity.MediaResourceActivity != null && resourceActivity.MediaResourceActivity.Any()) + { + viewModel.ActivityStatus = resourceActivity.MediaResourceActivity.First().PercentComplete == 100 ? ActivityStatusEnum.Completed : ActivityStatusEnum.InProgress; + viewModel.CompletionPercentage = Convert.ToInt32(resourceActivity.MediaResourceActivity.First().PercentComplete.GetValueOrDefault()); + viewModel.ActivityDurationSeconds = resourceActivity.MediaResourceActivity.First().SecondsPlayed.GetValueOrDefault(); + } + else if (viewModel.ActivityDate >= settings.DetailedMediaActivityRecordingStartDate) + { + // If media activity is from after the date when detailed activity recording began it should have an MRA. If it doesn't something has gone wrong. + viewModel.ActivityStatus = ActivityStatusEnum.InProgress; + } + + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) + { + viewModel.ResourceDurationMilliseconds = resourceActivity.ResourceVersion.VideoResourceVersion.DurationInMilliseconds.GetValueOrDefault(); + } + else if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio) + { + viewModel.ResourceDurationMilliseconds = resourceActivity.ResourceVersion.AudioResourceVersion.DurationInMilliseconds.GetValueOrDefault(); + } + } + + if (resourceActivity.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment) + { + var activity = resourceActivity.AssessmentResourceActivity.Count() > 0 ? resourceActivity.AssessmentResourceActivity.First() : null; + if (activity == null) + { + viewModel.Complete = false; + viewModel.CompletionPercentage = 0; + viewModel.AssessmentDetails = new MyLearningAssessmentDetails + { + PassMark = resourceActivity.ResourceVersion.AssessmentResourceVersion.PassMark, + }; + } + else + { + viewModel.AssessmentResourceActivityId = activity.Id; + viewModel.Complete = activity.Score != null; + viewModel.ScorePercentage = activity.Score.HasValue ? (int)Math.Round(activity.Score.Value, MidpointRounding.AwayFromZero) : 0; + + var currentAttempt = allAttempts.FindIndex(a => a.Id == resourceActivity.Id) + 1; + + var assessmemntActivityQuestion = await resourceActivityRepository.GetAssessmentActivityCompletionPercentage(resourceActivity.CreateUserId, resourceActivity.ResourceVersionId, resourceActivity.Id); + viewModel.CompletionPercentage = (int)assessmemntActivityQuestion.CompletionPercentage; + if (viewModel.CompletionPercentage == 100) + { + if (resourceActivity.ResourceVersion.AssessmentResourceVersion.AssessmentType == AssessmentTypeEnum.Informal) + { + viewModel.ActivityStatus = ActivityStatusEnum.Completed; + } + else + { + viewModel.ActivityStatus = viewModel.ScorePercentage >= resourceActivity.ResourceVersion.AssessmentResourceVersion.PassMark + ? ActivityStatusEnum.Passed + : ActivityStatusEnum.Failed; + } + } + else + { + viewModel.ActivityStatus = ActivityStatusEnum.InProgress; + } + + viewModel.AssessmentDetails = new MyLearningAssessmentDetails + { + ExtraAttemptReason = activity.Reason, + CurrentAttempt = currentAttempt, + MaximumAttempts = resourceActivity.ResourceVersion.AssessmentResourceVersion.MaximumAttempts, + PassMark = resourceActivity.ResourceVersion.AssessmentResourceVersion.PassMark, + }; + } + } + + viewModels.Add(viewModel); + } + + return viewModels; + } + + private IQueryable ApplyFilters(IQueryable query, MyLearningRequestModel requestModel) + { + // Text filter - Title, Keywords or Description. + if (!string.IsNullOrEmpty(requestModel.SearchText)) + { + query = query.Where(x => x.ResourceVersion.Title.Contains(requestModel.SearchText) || + x.ResourceVersion.Description.Contains(requestModel.SearchText) || + x.ResourceVersion.ResourceVersionKeyword.Any(y => y.Keyword.Contains(requestModel.SearchText))); + } + + // Resource Type filter. + if (requestModel.Article || requestModel.Audio || requestModel.Elearning || requestModel.Html || requestModel.File || requestModel.Image || requestModel.Video || requestModel.Weblink || requestModel.Assessment || requestModel.Case) + { + query = query.Where(x => + requestModel.Article && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Article || + requestModel.Audio && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || + requestModel.Elearning && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm || + requestModel.Html && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Html || + requestModel.File && x.Resource.ResourceTypeEnum == ResourceTypeEnum.GenericFile || + requestModel.Image && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Image || + requestModel.Video && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Video || + requestModel.Weblink && x.Resource.ResourceTypeEnum == ResourceTypeEnum.WebLink || + requestModel.Assessment && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment || + requestModel.Case && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Case); + } + + // Status Filter. + // All media activity prior to start of detailed media activity recording count as complete. + // All media activity after start of detailed media activity recording have to be 100% complete. + // Scorm treated as "in progress" when there is a launch event but no subsequent associated complete / passed / failed. + // Weblink, Article, Image treated as "complete" when there is a launch event. + if (requestModel.Complete || requestModel.Incomplete || requestModel.Passed || requestModel.Failed || requestModel.Downloaded) + { + query = query.Where(x => + requestModel.Complete && ((x.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || x.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) && + (x.ActivityStart.Value < settings.DetailedMediaActivityRecordingStartDate || + x.MediaResourceActivity != null && x.MediaResourceActivity.Any() && x.MediaResourceActivity.First().PercentComplete == 100) + || + (x.Resource.ResourceTypeEnum == ResourceTypeEnum.WebLink || x.Resource.ResourceTypeEnum == ResourceTypeEnum.Article || x.Resource.ResourceTypeEnum == ResourceTypeEnum.Image || x.Resource.ResourceTypeEnum == ResourceTypeEnum.Case) && x.ActivityStatusId == (int)ActivityStatusEnum.Launched + || + x.Resource.ResourceTypeEnum != ResourceTypeEnum.Audio && x.Resource.ResourceTypeEnum != ResourceTypeEnum.Video && x.ActivityStatusId == (int)ActivityStatusEnum.Completed) || + requestModel.Incomplete && ((x.Resource.ResourceTypeEnum == ResourceTypeEnum.Audio || x.Resource.ResourceTypeEnum == ResourceTypeEnum.Video) && + x.ActivityStart.Value >= settings.DetailedMediaActivityRecordingStartDate && + (x.MediaResourceActivity == null || !x.MediaResourceActivity.Any() || x.MediaResourceActivity.First().PercentComplete < 100) + || + //// Note - too slow to do this: (x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm && x.ScormActivity.FirstOrDefault() != null && x.ScormActivity.First().CmiCoreLessonStatus == (int)ActivityStatusEnum.InProgress) + //// - only returning "launched" for non-media when there is no subsequent event referencing the launch. + //// (x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm && x.ActivityStatusId == (int)ActivityStatusEnum.Launched) + x.Resource.ResourceTypeEnum == ResourceTypeEnum.Scorm && x.ScormActivity.Any() && x.ScormActivity.First().CmiCoreLessonStatus == (int)ActivityStatusEnum.InProgress + || + x.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment && x.AssessmentResourceActivity.Any() && !x.AssessmentResourceActivity.First().Score.HasValue) || + requestModel.Passed && x.ActivityStatusId == (int)ActivityStatusEnum.Passed || + requestModel.Passed && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment && x.AssessmentResourceActivity.Any() && x.AssessmentResourceActivity.First().Score >= x.ResourceVersion.AssessmentResourceVersion.PassMark || + requestModel.Failed && x.ActivityStatusId == (int)ActivityStatusEnum.Failed || + requestModel.Failed && x.Resource.ResourceTypeEnum == ResourceTypeEnum.Assessment && x.AssessmentResourceActivity.Any() && x.AssessmentResourceActivity.First().Score < x.ResourceVersion.AssessmentResourceVersion.PassMark || + requestModel.Downloaded && x.ActivityStatusId == (int)ActivityStatusEnum.Downloaded); + } + + // Date Filter. + if (!string.IsNullOrEmpty(requestModel.TimePeriod)) + { + var now = DateTime.Now.Date; + + if (requestModel.TimePeriod == "dateRange") + { + if (requestModel.StartDate.HasValue || requestModel.EndDate.HasValue) + { + if (requestModel.StartDate.HasValue) + { + query = query.Where(x => x.ActivityStart >= requestModel.StartDate); + } + + if (requestModel.EndDate.HasValue) + { + query = query.Where(x => x.ActivityStart < requestModel.EndDate.Value.AddDays(1)); + } + } + else + { + throw new ArgumentException("If RequestModel.TimePeriod is set to 'dateRange', the RequestModel.StartDate and/or EndDate must also be specified."); + } + } + else if (requestModel.TimePeriod == "thisWeek") + { + // Definition of this week is anything from the Monday prior, or today if today is Monday. + var firstDateOfWeek = now.FirstDateInWeek(DayOfWeek.Monday); + query = query.Where(x => x.ActivityStart >= firstDateOfWeek); + } + else if (requestModel.TimePeriod == "thisMonth") + { + // Definition of this month is anything from the 1st of the current month. + query = query.Where(x => x.ActivityStart >= new DateTime(now.Year, now.Month, 1)); + } + else if (requestModel.TimePeriod == "last12Months") + { + // Definition of the last 12 months is anything from the same date 12 months ago. e.g. if today is 7th Oct 2020, then return anything from 7th Oct 2019. + query = query.Where(x => x.ActivityStart > now.AddMonths(-12)); + } + } + + if (requestModel.CertificateEnabled) + { + query = query.Where(x => x.ResourceVersion.CertificateEnabled.Equals(true)); + } + + return query; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/PageService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/PageService.cs new file mode 100644 index 000000000..f5ca4aa12 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/PageService.cs @@ -0,0 +1,613 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Content; + using LearningHub.Nhs.Models.Entities.Content; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Enums.Content; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Content; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using LearningHub.Nhs.OpenApi.Services.Extensions; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + + /// + /// The PageService. + /// + public class PageService : IPageService + { + /// + /// Defines the mapper. + /// + private readonly IMapper mapper; + + /// + /// Defines the pageRepository. + /// + private readonly IPageRepository pageRepository; + + /// + /// Defines the pageSectionRepository. + /// + private readonly IPageSectionRepository pageSectionRepository; + + /// + /// Defines the pageSectionDetailRepository. + /// + private readonly IPageSectionDetailRepository pageSectionDetailRepository; + + /// + /// Defines the fileTypeService. + /// + private readonly IFileTypeService fileTypeService; + + /// + /// Defines the fileRepository. + /// + private readonly IFileRepository fileRepository; + + /// + /// Defines the videoAssetRepository. + /// + private readonly IVideoAssetRepository videoAssetRepository; + + /// + /// Defines the queueCommunicatorService. + /// + private readonly IQueueCommunicatorService queueCommunicatorService; + private readonly IInternalSystemService internalSystemService; + private readonly LearningHubConfig learningHubConfig; + + /// + /// Defines the settings. + /// + private readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// mapper. + /// pageRepository. + /// pageSectionRepository. + /// pageSectionDetailRepository. + /// fileTypeService. + /// fileRepository. + /// videoAssetRepository. + /// . + /// The internalSystemService. + /// The learningHub Config. + /// settings. + /// The logger. + public PageService( + IMapper mapper, + IPageRepository pageRepository, + IPageSectionRepository pageSectionRepository, + IPageSectionDetailRepository pageSectionDetailRepository, + IFileTypeService fileTypeService, + IFileRepository fileRepository, + IVideoAssetRepository videoAssetRepository, + IQueueCommunicatorService queueCommunicatorService, + IInternalSystemService internalSystemService, + IOptions learningHubConfig, + ILogger logger) + { + this.mapper = mapper; + this.pageRepository = pageRepository; + this.pageSectionRepository = pageSectionRepository; + this.pageSectionDetailRepository = pageSectionDetailRepository; + this.fileTypeService = fileTypeService; + this.fileRepository = fileRepository; + this.videoAssetRepository = videoAssetRepository; + this.queueCommunicatorService = queueCommunicatorService; + this.internalSystemService = internalSystemService; + this.learningHubConfig = learningHubConfig.Value; + } + + /// + /// The DiscardAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + public async Task DiscardAsync(int pageId, int currentUserId) + { + await pageRepository.DiscardAsync(pageId, currentUserId); + } + + /// + /// The GetPageById. + /// + /// The id. + /// includeHidden. + /// The published only. + /// Preview mode. + /// The . + public async Task GetPageByIdAsync(int id, bool includeHidden = false, bool publishedOnly = false, bool preview = false) + { + var page = await this.pageRepository.GetPageByIdAsync(id, publishedOnly, preview); + var pageViewModel = new PageViewModel + { + Id = page.Id, + Name = page.Name, + PreviewUrl = page.Url, + CanDiscard = page.CanDiscard(), + CanPreview = page.CanPreview(), + CanPublish = page.CanPublish(), + HasHiddenSections = page.HasHiddenSections(), + PageStatus = page.GetStatus(), + PageSections = page.PageSections.Select(ps => new PageSectionViewModel + { + Id = ps.Id, + IsHidden = ps.IsHidden, + Position = ps.Position, + AmendUserId = ps.AmendUserId, + SectionTemplateType = (SectionTemplateType)ps.SectionTemplateTypeId, + PageSectionDetail = mapper.Map(ps.PageSectionDetails.First()), + }) + .Where(ps => !publishedOnly || (ps.PageSectionDetail.DeletePending.HasValue ? !ps.PageSectionDetail.DeletePending.Value : true)) + .Where(ps => + !includeHidden + && (ps.PageSectionDetail.DraftHidden != null + ? !ps.PageSectionDetail.DraftHidden.Value + : !ps.IsHidden) + || includeHidden) + .OrderBy(ps => ps.PageSectionDetail.DraftPosition ?? ps.Position) + .ToList(), + }; + return pageViewModel; + } + + /// + /// GetPagesAsync. + /// + /// PageResultViewModel. + public async Task GetPagesAsync() + { + try + { + var pages = await pageRepository.GetPagesAsync(); + + var response = new PageResultViewModel + { + TotalCount = pages.Count(), + Pages = pages.Select(p => + new PageViewModel + { + Id = p.Id, + Name = p.Name, + PreviewUrl = p.Url, + PageStatus = p.PageSections.Any( + ps => (PageSectionStatus)ps.PageSectionDetails.First().PageSectionStatusId != PageSectionStatus.Live) ? PageStatus.EditsPending : PageStatus.Live, + }).ToList(), + }; + return response; + } + catch (Exception) + { + throw; + } + } + + /// + /// The GetPageSectionById. + /// + /// The id. + /// The . + public async Task GetPageSectionByIdAsync(int id) + { + var pageSection = await pageSectionRepository.GetByIdAsync(id); + return mapper.Map(pageSection); + } + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + public async Task GetPageSectionDetailImageAssetByIdAsync(int id) + { + var detail = await pageSectionDetailRepository.GetPageSectionDetailImageAssetByIdAsync(id); + + return mapper.Map(detail); + } + + /// + public async Task GetEditablePageSectionDetailByPageSectionIdAsync(int pageSectionId, int currentUserId) + { + PageSectionDetail detail = null; + + var pageSection = await pageSectionRepository.GetAll().Where(ps => ps.Id == pageSectionId) + .Include(ps => ps.PageSectionDetails.Where(psd => psd.PageSectionStatusId != (int)PageSectionStatus.Live).OrderByDescending(psd => psd.Id)) + .FirstOrDefaultAsync(); + + if (pageSection == null) + { + throw new Exception($"GetEditablePageSectionDetailByPageSectionIdAsync: PageSectionId {pageSectionId} not valid!"); + } + + if (!pageSection.PageSectionDetails.Any()) + { + var liveDetail = await pageSectionDetailRepository.GetAll() + .Where(psd => psd.PageSectionId == pageSectionId && psd.PageSectionStatusId == (int)PageSectionStatus.Live) + .FirstOrDefaultAsync(); + + detail = await pageSectionDetailRepository.CloneSectionDetailAsync(liveDetail.Id, (SectionTemplateType)pageSection.SectionTemplateTypeId, currentUserId); + } + else + { + if (pageSection.SectionTemplateTypeId == (int)SectionTemplateType.Video) + { + detail = await pageSectionDetailRepository.GetPageSectionDetailVideoAssetByIdAsync(pageSection.PageSectionDetails.First().Id); + } + else + { + detail = await pageSectionDetailRepository.GetPageSectionDetailImageAssetByIdAsync(pageSection.PageSectionDetails.First().Id); + } + } + + return mapper.Map(detail); + } + + /// + /// The PublishAsync. + /// + /// The pageId. + /// currentUserId. + /// The . + public async Task PublishAsync(int pageId, int currentUserId) + { + await pageRepository.PublishAsync(pageId, currentUserId); + } + + /// + /// Update page image section detail. + /// + /// The pageId. + /// The update model. + /// currentUserId. + /// The . + public async Task UpdatePageImageSectionDetailAsync(int pageId, PageImageSectionUpdateViewModel model, int currentUserId) + { + int? fileId = null; + + if (!string.IsNullOrWhiteSpace(model.ImageFilePath)) + { + var fileType = await fileTypeService.GetByFilename(model.ImageFileName); + + fileId = await fileRepository.CreateAsync(currentUserId, new File + { + FileTypeId = fileType == null ? 0 : fileType.Id, + FileName = model.ImageFileName, + FilePath = model.ImageFilePath, + FileSizeKb = model.ImageFileSize, + }); + } + + var detail = await pageSectionDetailRepository.GetPageSectionDetailImageAssetByIdAsync(model.PageSectionDetailId.Value); + + if (detail.ImageAsset == null) + { + detail.ImageAsset = new ImageAsset(); + } + + detail.PageSectionStatusId = (int)PageSectionStatus.Processed; + detail.BackgroundColour = model.BackgroundColour; + detail.TextColour = model.TextColour; + detail.HyperLinkColour = model.HyperLinkColour; + detail.Description = model.Description; + detail.TextBackgroundColour = model.TextBackgroundColour; + detail.SectionLayoutTypeId = (int)model.SectionLayoutType; + detail.ImageAsset.AltTag = model.ImageAlt; + detail.SectionTitle = model.SectionTitle; + detail.SectionTitleElement = model.SectionTitleElement; + detail.TopMargin = model.TopMargin; + detail.BottomMargin = model.BottomMargin; + detail.HasBorder = model.HasBorder; + + if (fileId.HasValue) + { + detail.ImageAsset.ImageFileId = fileId; + } + + await pageSectionDetailRepository.UpdateAsync(currentUserId, detail); + } + + /// + /// The CloneImageSectionAsync. + /// + /// The pageSectionId. + /// currentUserId. + /// The . + public async Task CloneAsync(int pageSectionId, int currentUserId) + { + var pageSection = await pageSectionRepository.GetByIdAsync(pageSectionId); + if ((SectionTemplateType)pageSection.SectionTemplateTypeId == SectionTemplateType.Image) + { + await pageSectionRepository.CloneImageSectionAsync(pageSectionId, currentUserId); + } + else if ((SectionTemplateType)pageSection.SectionTemplateTypeId == SectionTemplateType.Video) + { + await pageSectionRepository.CloneVideoSectionAsync(pageSectionId, currentUserId); + } + } + + /// + /// The ChangeOrderAsync. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + public async Task ChangeOrderAsync(UpdatePageSectionOrderModel requestViewModel, int currentUserId) + { + if (requestViewModel.DirectionType == DirectionType.Up) + { + await pageSectionRepository.ChangeOrderDownAsync(requestViewModel.PageId, requestViewModel.PageSectionId, currentUserId); + } + else + { + await pageSectionRepository.ChangeOrderUpAsync(requestViewModel.PageId, requestViewModel.PageSectionId, currentUserId); + } + } + + /// + /// The HideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task HideAsync(int pageSectionId, int currentUserId) + { + await pageSectionRepository.HideAsync(pageSectionId, currentUserId); + } + + /// + /// The UnHideAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task UnHideAsync(int pageSectionId, int currentUserId) + { + await pageSectionRepository.UnHideAsync(pageSectionId, currentUserId); + } + + /// + /// The DeleteAsync. + /// + /// The pageSectionId. + /// The currentUserId. + /// The . + public async Task DeleteAsync(int pageSectionId, int currentUserId) + { + await pageSectionRepository.DeleteAsync(pageSectionId, currentUserId); + } + + /// + /// The CreatePageSectionAsync. + /// + /// The requestViewModel. + /// currentUserId. + /// The page section id. + public async Task CreatePageSectionAsync(PageSectionViewModel requestViewModel, int currentUserId) + { + try + { + var pageSectionModel = mapper.Map(requestViewModel); + var pageSectionDetail = mapper.Map(requestViewModel.PageSectionDetail); + var pageSectionId = await pageSectionRepository.CreateWithPositionAsync(currentUserId, pageSectionModel); + pageSectionDetail.PageSectionId = pageSectionId; + + if (pageSectionModel.SectionTemplateTypeId == (int)SectionTemplateType.Video) + { + pageSectionDetail.VideoAsset = new VideoAsset { CreateUserId = currentUserId, AmendUserId = currentUserId }; + } + + pageSectionDetail.Id = await pageSectionDetailRepository.CreateAsync(currentUserId, pageSectionDetail); + return pageSectionId; + } + catch (Exception ex) + { + var errorMessage = "Error creating page section."; + logger.LogError(ex, errorMessage); + throw new Exception(errorMessage); + } + } + + /// + /// The save video asset async. + /// + /// The requestViewModel. + /// The userId. + /// The . + public async Task SaveVideoAssetAsync(FileCreateRequestViewModel requestViewModel, int userId) + { + File newFile = new File() + { + FileTypeId = requestViewModel.FileTypeId, + FileName = requestViewModel.FileName, + FilePath = requestViewModel.FilePath, + FileSizeKb = requestViewModel.FileSize, + FileChunkDetailId = requestViewModel.FileChunkDetailId, + }; + + int fileId = await fileRepository.CreateAsync(userId, newFile); + var videoAsset = await videoAssetRepository.GetByPageSectionDetailId(requestViewModel.PageSectionDetailId); + + var updateVideoAssetStateViewModel = new UpdateVideoAssetStateViewModel + { + PageSectionDetailId = requestViewModel.PageSectionDetailId, + UserId = userId, + PageSectionStatus = PageSectionStatus.Processing, + }; + + await UpdateVideoAssetStateAsync(updateVideoAssetStateViewModel); + + if (videoAsset == null) + { + videoAsset = new VideoAsset() + { + PageSectionDetailId = requestViewModel.PageSectionDetailId, + VideoFileId = fileId, + }; + await videoAssetRepository.CreateAsync(userId, videoAsset); + } + else + { + videoAsset.VideoFileId = fileId; + videoAsset.VideoFile = null; + videoAsset.AzureMediaAssetId = null; + videoAsset.AzureMediaAsset = null; + videoAsset.DurationInMilliseconds = null; + videoAsset.Deleted = false; + await videoAssetRepository.UpdateAsync(userId, videoAsset); + } + + var processVideoAssetQueueMessage = new ProcessVideoAssetQueueMessage + { + PageSectionDetailId = videoAsset.PageSectionDetailId, + VideoAssetFileId = videoAsset.VideoFileId.Value, + UserId = userId, + }; + var internalSystem = await internalSystemService.GetByIdAsync((int)InternalSystemType.ContentManagementQueue); + var contentManagementQueue = internalSystem.IsOffline ? $"{learningHubConfig.ContentManagementQueueName}-temp" : learningHubConfig.ContentManagementQueueName; + await queueCommunicatorService.SendAsync(contentManagementQueue, processVideoAssetQueueMessage); + var retVal = new LearningHubValidationResult(true) + { + CreatedId = fileId, + }; + return retVal; + } + + /// + /// The SaveAttributeFileDetails. + /// + /// The requestViewModel. + /// The currentUserId. + /// The . + public async Task SaveAttributeFileDetails(FileCreateRequestViewModel requestViewModel, int currentUserId) + { + var videoAsset = await videoAssetRepository.GetByPageSectionDetailId(requestViewModel.PageSectionDetailId); + int fileId = await fileRepository.CreateAsync( + currentUserId, + new File() + { + FileTypeId = requestViewModel.FileTypeId, + FileName = requestViewModel.FileName, + FilePath = requestViewModel.FilePath, + FileChunkDetailId = requestViewModel.FileChunkDetailId, + FileSizeKb = requestViewModel.FileSize, + }); + + switch (requestViewModel.AttachedFileType) + { + case AttachedFileTypeEnum.Transcript: + videoAsset.TranscriptFile = null; + videoAsset.TranscriptFileId = fileId; + break; + case AttachedFileTypeEnum.ClosedCaptions: + videoAsset.ClosedCaptionsFile = null; + videoAsset.ClosedCaptionsFileId = fileId; + break; + case AttachedFileTypeEnum.ThumbnailImage: + videoAsset.ThumbnailImageFile = null; + videoAsset.ThumbnailImageFileId = fileId; + break; + } + + await videoAssetRepository.UpdateAsync(currentUserId, videoAsset); + + var retVal = new LearningHubValidationResult(true) + { + CreatedId = fileId, + }; + return retVal; + } + + /// + /// The UpdatePageSectionDetailsAsync. + /// + /// The model. + /// The currentUserId. + /// The . + public async Task UpdatePageSectionDetailsAsync(PageSectionDetailViewModel model, int currentUserId) + { + var pageSectionDetail = await pageSectionDetailRepository.GetPageSectionDetailImageAssetByIdAsync(model.Id); + if (pageSectionDetail.PageSectionStatusId == (int)PageSectionStatus.Draft) + { + pageSectionDetail.PageSectionStatusId = (int)PageSectionStatus.Processed; + } + + pageSectionDetail.SectionTitle = model.SectionTitle; + pageSectionDetail.SectionTitleElement = model.SectionTitleElement; + pageSectionDetail.TopMargin = model.TopMargin; + pageSectionDetail.BottomMargin = model.BottomMargin; + pageSectionDetail.HasBorder = model.HasBorder; + pageSectionDetail.BackgroundColour = model.BackgroundColour; + pageSectionDetail.TextColour = model.TextColour; + pageSectionDetail.HyperLinkColour = model.HyperLinkColour; + pageSectionDetail.Description = model.Description; + pageSectionDetail.TextBackgroundColour = model.TextBackgroundColour; + pageSectionDetail.SectionLayoutTypeId = (int)model.SectionLayoutType; + await pageSectionDetailRepository.UpdateAsync(currentUserId, pageSectionDetail); + } + + /// + /// The UpdateVideoAssetAsync. + /// + /// videoAssetStateViewModel. + /// A representing the result of the asynchronous operation. + public async Task UpdateVideoAssetStateAsync(UpdateVideoAssetStateViewModel videoAssetStateViewModel) + { + var pageSectionDetail = await pageSectionDetailRepository.GetPageSectionDetailImageAssetByIdAsync(videoAssetStateViewModel.PageSectionDetailId); + pageSectionDetail.PageSectionStatusId = (int)videoAssetStateViewModel.PageSectionStatus; + await pageSectionDetailRepository.UpdateAsync(videoAssetStateViewModel.UserId, pageSectionDetail); + } + + /// + /// The GetPageSectionDetailVideoAssetByIdAsync. + /// + /// The id. + /// The . + public async Task GetPageSectionDetailVideoAssetByIdAsync(int id) + { + var detail = await pageSectionDetailRepository.GetPageSectionDetailVideoAssetByIdAsync(id); + + return mapper.Map(detail); + } + + /// + /// The UpdateVideoAssetManifestDetailsAsync. + /// + /// The viewModel. + /// The . + public async Task UpdateVideoAssetManifestDetailsAsync(UpdateVideoAssetManifestRequestViewModel viewModel) + { + var videoAsset = await videoAssetRepository.GetByPageSectionDetailId(viewModel.PageSectionDetailId); + videoAsset.AzureMediaAssetId = viewModel.AzureMediaAssetId; + videoAsset.DurationInMilliseconds = viewModel.DurationInMilliseconds; + await videoAssetRepository.UpdateAsync(viewModel.UserId, videoAsset); + } + + /// + /// The UpdateVideoAssetAsync. + /// + /// The viewModel. + /// The . + public async Task UpdateVideoAssetAsync(VideoAssetViewModel viewModel) + { + var videoAsset = await videoAssetRepository.GetById(viewModel.Id); + videoAsset.TranscriptFileId = viewModel.TranscriptFileId; + videoAsset.ClosedCaptionsFileId = viewModel.ClosedCaptionsFileId; + videoAsset.ThumbnailImageFileId = viewModel.ThumbnailImageFileId; + await videoAssetRepository.UpdateAsync(viewModel.AmendUserId, videoAsset); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceReferenceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceReferenceService.cs new file mode 100644 index 000000000..489216c94 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceReferenceService.cs @@ -0,0 +1,30 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + + /// + /// The ResourceReferenceService class. + /// + public class ResourceReferenceService : IResourceReferenceService + { + private readonly IResourceReferenceRepository resourceReferenceRepo; + + /// + /// Initializes a new instance of the class. + /// + /// The resourceReferenceRepo. + public ResourceReferenceService(IResourceReferenceRepository resourceReferenceRepo) + { + this.resourceReferenceRepo = resourceReferenceRepo; + } + + /// + public async Task GetByIdAsync(int id) + { + return await resourceReferenceRepo.GetByIdAsync(id, false); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index d03732124..e7c942996 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -3,6 +3,7 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using System; using System.Collections.Generic; using System.Data; + using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; @@ -10,15 +11,18 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using AutoMapper; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Specialized; + using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Constants; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Hierarchy; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Entities.Resource.Blocks; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Provider; using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Resource.Activity; + using LearningHub.Nhs.Models.Resource.Admin; using LearningHub.Nhs.Models.Resource.Blocks; using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.Models.Resource.Files; @@ -101,6 +105,8 @@ public class ResourceService : IResourceService private readonly IResourceVersionAuthorRepository resourceVersionAuthorRepository; private readonly IFileChunkDetailRepository fileChunkDetailRepository; private readonly IResourceSyncService resourceSyncService; + private readonly IResourceSyncRepository resourceSyncRepository; + private readonly IResourceVersionEventRepository resourceVersionEventRepository; /// /// Initializes a new instance of the class. @@ -163,7 +169,7 @@ public class ResourceService : IResourceService /// /// /// - public ResourceService(ILearningHubService learningHubService, IFileTypeService fileTypeService, IBlockCollectionRepository blockCollectionRepository, IInternalSystemService internalSystemService, IResourceVersionAuthorRepository resourceVersionAuthorRepository, IFileChunkDetailRepository fileChunkDetailRepository, IQueueCommunicatorService queueCommunicatorService, IResourceRepository resourceRepository, IResourceVersionProviderRepository resourceVersionProviderRepository, IProviderService providerService, IArticleResourceVersionFileRepository articleResourceVersionFileRepository, IPublicationRepository publicationRepository, IMigrationSourceRepository migrationSourceRepository, IQuestionBlockRepository questionBlockRepository, IVideoRepository videoRepository, IWholeSlideImageRepository wholeSlideImageRepository, IEmbeddedResourceVersionRepository embeddedResourceVersionRepository, IEquipmentResourceVersionRepository equipmentResourceVersionRepository, IImageResourceVersionRepository imageResourceVersionRepository, IBookmarkRepository bookmarkRepository, IAssessmentResourceActivityMatchQuestionRepository assessmentResourceActivityMatchQuestionRepository, IResourceVersionKeywordRepository resourceVersionKeywordRepository, IResourceVersionValidationResultRepository resourceVersionValidationResultRepository, ILogger logger, IWebLinkResourceVersionRepository webLinkResourceVersionRepository, ICaseResourceVersionRepository caseResourceVersionRepository, IScormResourceVersionRepository scormResourceVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, IResourceVersionRepository resourceVersionRepository, IHtmlResourceVersionRepository htmlResourceVersionRepository, IMapper mapper, IFileRepository fileRepository, IOptions azureConfig, IOptions learningHubConfig, IUserProfileService userProfileService, IResourceVersionFlagRepository resourceVersionFlagRepository, IArticleResourceVersionRepository articleResourceVersionRepository, IAudioResourceVersionRepository audioResourceVersionRepository, IVideoResourceVersionRepository videoResourceVersionRepository, IAssessmentResourceVersionRepository assessmentResourceVersionRepository, IResourceLicenceRepository resourceLicenceRepository, IResourceReferenceRepository resourceReferenceRepository, IResourceVersionUserAcceptanceRepository resourceVersionUserAcceptanceRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, ICachingService cachingService, ISearchService searchService, ICatalogueService catalogueService, INodeResourceRepository nodeResourceRepository, INodePathRepository nodePathRepository, IUserService userService, INodeRepository nodeRepository, IResourceSyncService resourceSyncService, LearningHubDbContext dbContext) + public ResourceService(ILearningHubService learningHubService, IFileTypeService fileTypeService, IBlockCollectionRepository blockCollectionRepository, IInternalSystemService internalSystemService, IResourceVersionAuthorRepository resourceVersionAuthorRepository, IFileChunkDetailRepository fileChunkDetailRepository, IQueueCommunicatorService queueCommunicatorService, IResourceRepository resourceRepository, IResourceVersionProviderRepository resourceVersionProviderRepository, IProviderService providerService, IArticleResourceVersionFileRepository articleResourceVersionFileRepository, IPublicationRepository publicationRepository, IMigrationSourceRepository migrationSourceRepository, IQuestionBlockRepository questionBlockRepository, IVideoRepository videoRepository, IWholeSlideImageRepository wholeSlideImageRepository, IEmbeddedResourceVersionRepository embeddedResourceVersionRepository, IEquipmentResourceVersionRepository equipmentResourceVersionRepository, IImageResourceVersionRepository imageResourceVersionRepository, IBookmarkRepository bookmarkRepository, IAssessmentResourceActivityMatchQuestionRepository assessmentResourceActivityMatchQuestionRepository, IResourceVersionKeywordRepository resourceVersionKeywordRepository, IResourceVersionValidationResultRepository resourceVersionValidationResultRepository, ILogger logger, IWebLinkResourceVersionRepository webLinkResourceVersionRepository, ICaseResourceVersionRepository caseResourceVersionRepository, IScormResourceVersionRepository scormResourceVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, IResourceVersionRepository resourceVersionRepository, IHtmlResourceVersionRepository htmlResourceVersionRepository, IMapper mapper, IFileRepository fileRepository, IOptions azureConfig, IOptions learningHubConfig, IUserProfileService userProfileService, IResourceVersionFlagRepository resourceVersionFlagRepository, IArticleResourceVersionRepository articleResourceVersionRepository, IAudioResourceVersionRepository audioResourceVersionRepository, IVideoResourceVersionRepository videoResourceVersionRepository, IAssessmentResourceVersionRepository assessmentResourceVersionRepository, IResourceLicenceRepository resourceLicenceRepository, IResourceReferenceRepository resourceReferenceRepository, IResourceVersionUserAcceptanceRepository resourceVersionUserAcceptanceRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, ICachingService cachingService, ISearchService searchService, ICatalogueService catalogueService, INodeResourceRepository nodeResourceRepository, INodePathRepository nodePathRepository, IUserService userService, INodeRepository nodeRepository, IResourceSyncService resourceSyncService, IResourceSyncRepository resourceSyncRepository, IResourceVersionEventRepository resourceVersionEventRepository, LearningHubDbContext dbContext) { this.learningHubService = learningHubService; this.resourceRepository = resourceRepository; @@ -218,9 +224,10 @@ public ResourceService(ILearningHubService learningHubService, IFileTypeService this.videoRepository = videoRepository; this.fileTypeService = fileTypeService; this.resourceSyncService = resourceSyncService; + this.resourceSyncRepository = resourceSyncRepository; + this.resourceVersionEventRepository = resourceVersionEventRepository; } - /// /// the get by id async. /// @@ -718,6 +725,63 @@ public async Task GetResourceVersionByResourceReferenc return await this.GetResourceVersionViewModelAsync(rv.Id); } + /// + /// Returns a list of basic resource info - filtered, sorted and paged as required. + /// + /// The userId. + /// The request detail . + /// The . + public async Task> GetResourceAdminSearchFilteredPageAsync(int currentUserId, PagingRequestModel pagingRequestModel) + { + PagedResultSet result = new PagedResultSet(); + + var items = this.resourceVersionRepository.GetAllAdminSearch(currentUserId); + + items = this.FilterItems(items, pagingRequestModel.Filter); + + result.TotalItemCount = items.Count(); + + items = this.OrderItems(items, pagingRequestModel.SortColumn, pagingRequestModel.SortDirection); + + items = items.Skip((pagingRequestModel.Page - 1) * pagingRequestModel.PageSize).Take(pagingRequestModel.PageSize); + + result.Items = await this.mapper.ProjectTo(items).ToListAsync(); + + var resourceSyncs = this.resourceSyncRepository.GetSyncListForUser(currentUserId, false); + foreach (var item in result.Items) + { + item.MarkedForSync = resourceSyncs.Any(x => x.ResourceId == item.ResourceVersionId); + } + + return result; + } + + /// + /// Transfer Resource Ownership. + /// + /// The transferResourceOwnershipViewModel. + /// The userId. + /// The . + public async Task TransferResourceOwnership(TransferResourceOwnershipViewModel transferResourceOwnershipViewModel, int userId) + { + var vr = new LearningHubValidationResult(true); + + var newOwner = await this.userService.GetByUsernameAsync(transferResourceOwnershipViewModel.NewOwnerUsername); + if (newOwner == null) + { + vr.Add(new LearningHubValidationResult(false, $"New Owner Username '{transferResourceOwnershipViewModel.NewOwnerUsername}' does not exist")); + } + + await this.CheckAndRemoveResourceVersionProviderAsync(transferResourceOwnershipViewModel.ResourceVersionId, newOwner.Id); + + if (vr.IsValid) + { + this.resourceRepository.TransferResourceOwnership(transferResourceOwnershipViewModel.ResourceId, transferResourceOwnershipViewModel.NewOwnerUsername, userId); + } + + return vr; + } + /// /// The unpublish resource version. /// @@ -2184,6 +2248,320 @@ public async Task DuplicateResourceAsync(DuplicateR } } + /// + /// Get file directory for unpublished or deleted versions. + /// + /// The resourceVersionId. + /// . + /// The . + public async Task> GetObsoleteResourceFile(int resourceVersionId, bool deletedResource = false) + { + var retVal = new List(); + var resourceVersion = await this.GetResourceVersionByIdAsync(resourceVersionId); + if (resourceVersion != null) + { + if (resourceVersion.ResourceType == ResourceTypeEnum.Assessment) + { + if (deletedResource) + { + var assessmentFiles = await this.GetResourceBlockCollectionsFilePathAsync(resourceVersionId, ResourceTypeEnum.Assessment); + if (assessmentFiles.Any()) + { + retVal.AddRange(assessmentFiles); + } + } + else + { + var assessmentDetails = await this.GetAssessmentViewModel(resourceVersionId); + var rvs = await this.resourceVersionRepository.GetResourceVersionsAsync(resourceVersion.ResourceId); + rvs = rvs.Where(x => x.Id != resourceVersionId && x.PublicationId > 0).OrderByDescending(x => x.PublicationId).ToList(); + var rv = rvs.FirstOrDefault(); + var currentAssessmentVersion = await this.GetAssessmentViewModel(rv.Id); + List deletableFiles = null; + if (assessmentDetails is { EndGuidance: { } } && assessmentDetails.EndGuidance.Blocks != null) + { + var endGuidanceFiles = this.CheckBlockFile(assessmentDetails.EndGuidance, currentAssessmentVersion.EndGuidance); + if (endGuidanceFiles.Any()) + { + deletableFiles = await this.GetResourceBlockCollectionsFilePathAsync(rv.Id, ResourceTypeEnum.Assessment); + retVal.AddRange(from entry in endGuidanceFiles + where deletableFiles.Contains(entry) + select entry); + } + } + + if (assessmentDetails is { AssessmentContent: { } } && assessmentDetails.AssessmentContent.Blocks != null) + { + var assessmentContentFiles = this.CheckBlockFile(assessmentDetails.AssessmentContent, currentAssessmentVersion.AssessmentContent); + if (assessmentContentFiles.Any()) + { + deletableFiles ??= await this.GetResourceBlockCollectionsFilePathAsync(rv.Id, ResourceTypeEnum.Assessment); + retVal.AddRange(from entry in assessmentContentFiles + where deletableFiles.Contains(entry) + select entry); + } + } + } + } + else if (resourceVersion.ResourceType == ResourceTypeEnum.Case) + { + var caseFiles = new List(); + if (deletedResource) + { + caseFiles = await this.GetResourceBlockCollectionsFilePathAsync(resourceVersionId, ResourceTypeEnum.Case); + } + else + { + var caseDetails = await this.GetCaseDetailsByIdAsync(resourceVersionId); + var rvs = await this.resourceVersionRepository.GetResourceVersionsAsync(resourceVersion.ResourceId); + rvs = rvs.Where(x => x.Id != resourceVersionId && x.PublicationId > 0).OrderByDescending(x => x.PublicationId).ToList(); + var rv = rvs.FirstOrDefault(); + if (rv != null) + { + var currentCaseVersion = await this.GetCaseDetailsByIdAsync(rv.Id); + var nonpublishedFiles = this.CheckBlockFile(caseDetails.BlockCollection, currentCaseVersion.BlockCollection); + if (nonpublishedFiles.Any()) + { + var deletableCaseFiles = await this.GetResourceBlockCollectionsFilePathAsync(rv.Id, ResourceTypeEnum.Case); + caseFiles.AddRange(from entry in nonpublishedFiles + where deletableCaseFiles.Contains(entry) + select entry); + } + } + } + + if (caseFiles.Any()) + { + retVal.AddRange(caseFiles); + } + } + else + { + // get latest published resource version + var rvs = await this.resourceVersionRepository.GetResourceVersionsAsync(resourceVersion.ResourceId); + rvs = rvs.Where(x => x.Id != resourceVersionId && x.PublicationId > 0).OrderByDescending(x => x.PublicationId).ToList(); + var rv = rvs.FirstOrDefault(); + var extendedResourceVersion = rv != null ? await this.GetResourceVersionExtendedViewModelAsync(rv.Id) : null; + switch (resourceVersion.ResourceType) + { + case ResourceTypeEnum.Scorm: + var scormResource = await this.GetScormDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (scormResource?.ContentFilePath != extendedResourceVersion.ScormDetails.ContentFilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.ScormDetails.ContentFilePath); + } + else + { + retVal.Add(scormResource?.File?.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(scormResource?.File?.FilePath); + } + + break; + case ResourceTypeEnum.Html: + var htmlResource = await this.GetHtmlDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (htmlResource?.ContentFilePath != extendedResourceVersion.HtmlDetails.ContentFilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.HtmlDetails.ContentFilePath); + } + else + { + retVal.Add(htmlResource?.File?.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(htmlResource?.File?.FilePath); + } + + break; + case ResourceTypeEnum.GenericFile: + var fileResource = await this.GetGenericFileDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (fileResource?.File.FilePath != extendedResourceVersion.GenericFileDetails.File.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.GenericFileDetails.File.FilePath); + } + else + { + retVal.Add(fileResource?.File?.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(fileResource.File.FilePath); + } + + break; + case ResourceTypeEnum.Image: + var imageResource = await this.GetImageDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (imageResource?.File.FilePath != extendedResourceVersion.ImageDetails.File.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.ImageDetails.File.FilePath); + } + else + { + retVal.Add(imageResource.File.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(imageResource.File.FilePath); + } + + break; + case ResourceTypeEnum.Audio: + var audioResource = await this.GetAudioDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (audioResource?.File?.FilePath != extendedResourceVersion.AudioDetails.File.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.AudioDetails.File.FilePath); + } + else + { + retVal.Add(audioResource.File.FilePath); + } + } + + if (audioResource?.ResourceAzureMediaAsset?.FilePath != extendedResourceVersion.AudioDetails.ResourceAzureMediaAsset.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.AudioDetails.ResourceAzureMediaAsset.FilePath); + } + else + { + retVal.Add(audioResource.ResourceAzureMediaAsset.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(audioResource?.File?.FilePath); + if (audioResource?.ResourceAzureMediaAsset?.FilePath != null) + { + retVal.Add(audioResource.ResourceAzureMediaAsset.FilePath); + } + } + + break; + case ResourceTypeEnum.Video: + var videoResource = await this.GetVideoDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + if (videoResource?.File?.FilePath != extendedResourceVersion.VideoDetails.File.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.VideoDetails.File.FilePath); + } + else + { + retVal.Add(videoResource.File.FilePath); + } + } + + if (videoResource?.ResourceAzureMediaAsset?.FilePath != extendedResourceVersion.VideoDetails.ResourceAzureMediaAsset.FilePath) + { + if (!deletedResource) + { + retVal.Add(extendedResourceVersion.VideoDetails.ResourceAzureMediaAsset.FilePath); + } + else + { + retVal.Add(videoResource.ResourceAzureMediaAsset.FilePath); + } + } + } + else if (rv == null && deletedResource) + { + retVal.Add(videoResource?.File?.FilePath); + if (videoResource?.ResourceAzureMediaAsset?.FilePath != null) + { + retVal.Add(videoResource.ResourceAzureMediaAsset.FilePath); + } + } + + break; + case ResourceTypeEnum.Article: + var articleResource = await this.GetArticleDetailsByIdAsync(resourceVersionId); + if (rv != null) + { + var inputResourceFiles = articleResource.Files.ToList(); + var previousPublishedfiles = extendedResourceVersion.ArticleDetails?.Files?.ToList(); + if (!deletedResource) + { + if (previousPublishedfiles.Any()) + { + foreach (var file in previousPublishedfiles) + { + if (!inputResourceFiles.Where(x => x.FilePath == file.FilePath).Any()) + { + retVal.Add(file.FilePath); + } + } + } + } + else + { + if (inputResourceFiles.Any()) + { + foreach (var file in inputResourceFiles) + { + if (!previousPublishedfiles.Where(x => x.FilePath == file.FilePath).Any()) + { + retVal.Add(file.FilePath); + } + } + } + } + } + else if (rv == null && deletedResource) + { + var inputResourceFiles = articleResource.Files.ToList(); + if (inputResourceFiles.Any()) + { + foreach (var file in inputResourceFiles) + { + retVal.Add(file.FilePath); + } + } + } + + break; + } + } + } + + return retVal.Distinct().ToList(); + } + + /// /// Creation of a new resource version. /// @@ -2797,6 +3175,27 @@ public async Task UpdateWebLinkResourceVersionAsync return result; } + /// + /// The get case resource version async. + /// + /// The resource version id. + /// The resource type. + /// The . + public async Task> GetResourceBlockCollectionsFilePathAsync(int excludeResourceVersionId, ResourceTypeEnum resourceType) + { + var pathList = new List(); + var collection = await this.blockCollectionRepository.GetResourceBlockCollectionsFileAsync(excludeResourceVersionId, resourceType); + if (!string.IsNullOrWhiteSpace(collection)) + { + pathList = collection.Split(',') + .Select(item => item.Trim()) + .Where(item => !string.IsNullOrEmpty(item)) + .ToList(); + } + + return pathList; + } + /// /// The update case resource version async. /// @@ -3097,6 +3496,24 @@ public async Task CreateResourceVersionProviderAsyn } } + /// + /// The create resource version validation result async. + /// + /// The validationResultViewModel. + /// The . + public async Task CreateResourceVersionValidationResultAsync(ResourceVersionValidationResultViewModel validationResultViewModel) + { + var resourceVersionValidationResult = this.mapper.Map(validationResultViewModel); + var id = await this.resourceVersionValidationResultRepository.CreateAsync(validationResultViewModel.AmendUserId, resourceVersionValidationResult); + + var vr = new LearningHubValidationResult() + { + CreatedId = id, + IsValid = true, + }; + + return vr; + } /// /// Set the resource catalogue. @@ -3751,5 +4168,179 @@ private async Task CreateAudioDetailsAsync(int resourceVersionId, File newFile, } } + /// + /// Filter the items for resource version search. + /// + /// The items. + /// The filterCriteria. + /// The . + private IQueryable FilterItems(IQueryable items, List filterCriteria) + { + if (filterCriteria == null || filterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in filterCriteria) + { + switch (filter.Column.ToLower()) + { + case "versionid": + items = items.Where(x => x.Id == int.Parse(filter.Value)); + break; + case "referenceid": + items = items.Where(x => x.Resource.ResourceReference.Any(rr => rr.OriginalResourceReferenceId == int.Parse(filter.Value) && !rr.Deleted)); + break; + case "title": + items = items.Where(x => x.Title.Contains(filter.Value)); + break; + case "createuser": + items = items.Where(x => x.CreateUser.UserName.Contains(filter.Value)); + break; + case "userid": + // Used by User Detail screen Contributions tab to filter by userId. + items = items.Where(x => x.CreateUser.Id == int.Parse(filter.Value)); + break; + case "createdate": + var filteredDate = DateTime.ParseExact(filter.Value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + items = items.Where(x => x.CreateDate >= filteredDate && x.CreateDate < filteredDate.AddDays(1)); + break; + case "type": + if (Enum.TryParse(filter.Value, out var resourceType)) + { + items = items.Where(x => x.Resource.ResourceTypeEnum == resourceType); + } + + break; + case "status": + if (Enum.TryParse(filter.Value, out var statusType)) + { + items = items.Where(x => x.VersionStatusEnum == statusType); + } + + break; + default: + break; + } + } + + return items; + } + + /// + /// Order the items for resource version search. + /// + /// The items. + /// The sortColumn. + /// The sortDirection. + /// The . + private IQueryable OrderItems(IQueryable items, string sortColumn, string sortDirection) + { + switch (sortColumn.ToLower()) + { + case "title": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.Title); + } + else + { + items = items.OrderBy(x => x.Title); + } + + break; + case "createuser": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.CreateUser.UserName); + } + else + { + items = items.OrderBy(x => x.CreateUser.UserName); + } + + break; + case "referenceid": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.Resource.ResourceReference.First().OriginalResourceReferenceId); + } + else + { + items = items.OrderBy(x => x.Resource.ResourceReference.First().OriginalResourceReferenceId); + } + + break; + default: + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.Id); + } + else + { + items = items.OrderBy(x => x.Id); + } + + break; + } + + return items; + } + + /// + /// The get resource version events async. + /// + /// The resourceVersionId. + /// The . + public async Task> GetResourceVersionEventsAsync(int resourceVersionId) + { + var res = this.resourceVersionEventRepository.GetByResourceVersionIdAsync(resourceVersionId); + + var retVal = await this.mapper.ProjectTo(res).ToListAsync(); + + return retVal; + } + + /// + /// The get resource version dev id async. + /// + /// The resourceVersionId. + /// The . + public async Task GetResourceVersionDevIdDetailsAync(int resourceVersionId) + { + ResourceVersionDevIdViewModel resourceVersionDevIdViewModel = new ResourceVersionDevIdViewModel(); + var res = await this.resourceVersionRepository.GetByResourceVersionByIdAsync(resourceVersionId); + resourceVersionDevIdViewModel.ResourceVersionId = res.Id; + resourceVersionDevIdViewModel.DevId = res.DevId; + return resourceVersionDevIdViewModel; + } + + /// + /// The get resource version dev id async. + /// + /// The devId. + /// The . + public async Task DoesDevIdExistsAync(string devId) + { + var res = await this.resourceVersionRepository.DoesDevIdExistsAync(devId); + + if (res != null) + { + return true; + } + + return false; + } + + /// + /// Update Dev Id details. + /// + /// The resourceVersionDevIdViewModel. + /// The currentUserId. + /// The . + public async Task UpdateDevIdDetailsAsync(ResourceVersionDevIdViewModel resourceVersionDevIdViewModel, int currentUserId) + { + await this.resourceVersionRepository.UpdateDevIdAsync(currentUserId, resourceVersionDevIdViewModel); + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index 09a0fb3d0..99f0e2830 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -7,9 +7,13 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using System.Text; using System.Threading.Tasks; using System.Web; + using AutoMapper; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Search.SearchClick; + using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.Models.ViewModels.Helpers; using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Models.ServiceModels.Findwise; @@ -22,17 +26,31 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; + using Event = LearningHub.Nhs.Models.Entities.Analytics.Event; /// /// The search service. /// public class SearchService : ISearchService { + + /// + /// application id for search. + /// + private const string ApplicationId = "HEE"; + + /// + /// profile type for search. + /// + private const string ProfileType = "SEARCHER"; + + private readonly IEventService eventService; private readonly ILearningHubService learningHubService; private readonly IResourceRepository resourceRepository; private readonly IFindwiseClient findwiseClient; private readonly ILogger logger; private readonly FindwiseConfig findwiseConfig; + private readonly IMapper mapper; /// /// Initializes a new instance of the class. @@ -53,18 +71,392 @@ public class SearchService : ISearchService /// Logger. public SearchService( ILearningHubService learningHubService, + IEventService eventService, IFindwiseClient findwiseClient, IOptions findwiseConfig, IResourceRepository resourceRepository, - ILogger logger) + ILogger logger, + IMapper mapper) { this.learningHubService = learningHubService; + this.eventService = eventService; this.findwiseClient = findwiseClient; this.resourceRepository = resourceRepository; this.logger = logger; + this.mapper = mapper; this.findwiseConfig = findwiseConfig.Value; } + /// + /// The Get Search Result Async method. + /// + /// The search request model. + /// The user id. + /// The . + public async Task GetSearchResultAsync(SearchRequestModel searchRequestModel, int userId) + { + SearchResultModel viewmodel = new SearchResultModel(); + + try + { + // e.g. if pagesize is 10, then offset would be 0,10,20,30 + var pageSize = searchRequestModel.PageSize; + var offset = searchRequestModel.PageIndex * pageSize; + + var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); + + var request = string.Format( + this.findwiseConfig.SearchEndpointPath + "?offset={1}&hits={2}&q={3}&token={4}", + this.findwiseConfig.CollectionIds.Resource, + offset, + pageSize, + this.EncodeSearchText(searchRequestModel.SearchText) + searchRequestModel.FilterText + searchRequestModel.ResourceAccessLevelFilterText + searchRequestModel.ProviderFilterText, + this.findwiseConfig.Token); + + if (searchRequestModel.CatalogueId.HasValue) + { + request += $"&catalogue_ids={searchRequestModel.CatalogueId}"; + } + + // if sort column is requested + if (!string.IsNullOrEmpty(searchRequestModel.SortColumn)) + { + var sortquery = $"&sort={searchRequestModel.SortColumn}"; + + // if sort direction option is requested + if (!string.IsNullOrEmpty(searchRequestModel.SortDirection)) + { + var sortdirection = searchRequestModel.SortDirection.StartsWith("asc") ? "asc" : "desc"; + sortquery = $"{sortquery}_{sortdirection}"; + } + + request = $"{request}{sortquery}"; + } + + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + searchRequestModel.TotalNumberOfHits = viewmodel.Stats.TotalHits; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.logger.LogError($"Get Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}"); + throw new Exception("AccessDenied to FindWise Server"); + } + else + { + var error = response.Content.ReadAsStringAsync().Result.ToString(); + this.logger.LogError($"Get Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); + throw new Exception("Error with FindWise Server"); + } + + return viewmodel; + } + catch (Exception) + { + throw; + } + } + + /// + /// The Get Catalogue Search Result Async method. + /// + /// + /// The catalog search request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + public async Task GetCatalogueSearchResultAsync(CatalogueSearchRequestModel catalogSearchRequestModel, int userId) + { + var viewmodel = new SearchCatalogueResultModel(); + + try + { + // e.g. if pagesize is 3, then offset would be 0,3,6,9 + var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; + var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); + var request = string.Format( + this.findwiseConfig.SearchEndpointPath + "?offset={1}&hits={2}&q={3}&token={4}", + this.findwiseConfig.CollectionIds.Catalogue, + offset, + catalogSearchRequestModel.PageSize, + this.EncodeSearchText(catalogSearchRequestModel.SearchText), + this.findwiseConfig.Token); + + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + catalogSearchRequestModel.TotalNumberOfHits = viewmodel.Stats.TotalHits; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.logger.LogError($"Get Catalogue Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}"); + throw new Exception("AccessDenied to FindWise Server"); + } + else + { + var error = response.Content.ReadAsStringAsync().Result.ToString(); + this.logger.LogError($"Get Catalogue Search Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); + throw new Exception("Error with FindWise Server"); + } + + var remainingItems = catalogSearchRequestModel.TotalNumberOfHits - offset; + var resultsPerPage = remainingItems >= catalogSearchRequestModel.PageSize ? catalogSearchRequestModel.PageSize : remainingItems; + LearningHubValidationResult validationResult = await this.CreateCatalogueSearchTerm(catalogSearchRequestModel, resultsPerPage, userId); + + viewmodel.SearchId = validationResult.CreatedId ?? 0; + + return viewmodel; + } + catch (Exception) + { + throw; + } + } + + /// + /// The create resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + public async Task CreateResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId) + { + var jsonobj = new + { + searchActionResourceModel.SearchText, + searchActionResourceModel.NodePathId, + searchActionResourceModel.ItemIndex, + searchActionResourceModel.NumberOfHits, + searchActionResourceModel.TotalNumberOfHits, + searchActionResourceModel.ResourceReferenceId, + }; + + var json = JsonConvert.SerializeObject(jsonobj); + + var eventEntity = new Event + { + EventTypeEnum = EventTypeEnum.SearchLaunchResource, + JsonData = json, + UserId = userId, + GroupId = searchActionResourceModel.GroupId, + }; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + /// + /// The create catalogue search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + public async Task CreateCatalogueSearchActionAsync(SearchActionCatalogueModel searchActionCatalogueModel, int userId) + { + var jsonobj = new + { + searchActionCatalogueModel.SearchText, + searchActionCatalogueModel.NodePathId, + searchActionCatalogueModel.ItemIndex, + searchActionCatalogueModel.NumberOfHits, + searchActionCatalogueModel.TotalNumberOfHits, + searchActionCatalogueModel.CatalogueId, + }; + + var json = JsonConvert.SerializeObject(jsonobj); + + var eventEntity = new Event(); + eventEntity.EventTypeEnum = EventTypeEnum.SearchLaunchCatalogue; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = searchActionCatalogueModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + /// + /// The create catalogue resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The user id. + /// + /// + /// The . + /// + public async Task CreateCatalogueResourceSearchActionAsync(SearchActionResourceModel searchActionResourceModel, int userId) + { + var jsonobj = new + { + searchActionResourceModel.SearchText, + searchActionResourceModel.NodePathId, + searchActionResourceModel.ItemIndex, + searchActionResourceModel.ResourceReferenceId, + searchActionResourceModel.NumberOfHits, + searchActionResourceModel.TotalNumberOfHits, + }; + + var json = JsonConvert.SerializeObject(jsonobj); + + var eventEntity = new Event(); + eventEntity.EventTypeEnum = EventTypeEnum.LaunchCatalogueResource; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = searchActionResourceModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + /// + /// The submt feedback async. + /// + /// The search feedback. + /// The user id. + /// The event id. + public async Task SubmitFeedbackAsync(SearchFeedBackModel searchFeedbackModel, int userId) + { + var jsonobj = new + { + searchFeedbackModel.SearchText, + searchFeedbackModel.Feedback, + searchFeedbackModel.TotalNumberOfHits, + }; + + var json = JsonConvert.SerializeObject(jsonobj); + + var eventEntity = new Event(); + eventEntity.EventTypeEnum = EventTypeEnum.SearchSubmitFeedback; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = searchFeedbackModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + /// + /// Create search term event entry. + /// + /// search request model. + /// user id. + /// The . + public async Task CreateSearchTermEvent(SearchRequestModel searchRequestModel, int userId) + { + // e.g. if pagesize is 10, then offset would be 0,10,20,30 + var pageSize = searchRequestModel.PageSize; + var offset = searchRequestModel.PageIndex * pageSize; + + var remainingItems = searchRequestModel.TotalNumberOfHits - offset; + var resultsPerPage = remainingItems >= pageSize ? pageSize : remainingItems; + + var searchEventModel = this.mapper.Map(searchRequestModel); + searchEventModel.ItemsViewed = resultsPerPage; + var json = JsonConvert.SerializeObject(searchEventModel); + + var eventEntity = new Event(); + eventEntity.EventTypeEnum = searchRequestModel.EventTypeEnum; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = searchRequestModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + /// + /// Create catalogue search term event. + /// + /// catalogue search request model. + /// user id. + /// The . + public async Task CreateCatalogueSearchTermEvent(CatalogueSearchRequestModel catalogueSearchRequestModel, int userId) + { + // e.g. if pagesize is 3, then offset would be 0,3,6,9 + var offset = catalogueSearchRequestModel.PageIndex * catalogueSearchRequestModel.PageSize; + + var remainingItems = catalogueSearchRequestModel.TotalNumberOfHits - offset; + var resultsPerPage = remainingItems >= catalogueSearchRequestModel.PageSize ? catalogueSearchRequestModel.PageSize : remainingItems; + + var searchCatalogueEventModel = this.mapper.Map(catalogueSearchRequestModel); + searchCatalogueEventModel.ItemsViewed = resultsPerPage; + var json = JsonConvert.SerializeObject(searchCatalogueEventModel); + var eventEntity = new Event(); + eventEntity.EventTypeEnum = catalogueSearchRequestModel.EventTypeEnum; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = catalogueSearchRequestModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + + /// + /// The create resource search action async. + /// + /// + /// The search action request model. + /// + /// + /// The . + /// + public async Task SendResourceSearchEventClickAsync(SearchActionResourceModel searchActionResourceModel) + { + var searchClickPayloadModel = this.mapper.Map(searchActionResourceModel); + searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.Resource; + + return await this.SendSearchEventClickAsync(searchClickPayloadModel, true); + } + + /// + /// Create catalogue search term. + /// + /// catalogue search request model. + /// results per page. + /// user id. + /// + /// The . + /// + public async Task CreateCatalogueSearchTerm(CatalogueSearchRequestModel catalogueSearchRequestModel, int resultsPerPage, int userId) + { + var searchCatalogueEventModel = this.mapper.Map(catalogueSearchRequestModel); + searchCatalogueEventModel.ItemsViewed = resultsPerPage; + var json = JsonConvert.SerializeObject(searchCatalogueEventModel); + var eventEntity = new Event(); + eventEntity.EventTypeEnum = catalogueSearchRequestModel.EventTypeEnum; + eventEntity.JsonData = json; + eventEntity.UserId = userId; + eventEntity.GroupId = catalogueSearchRequestModel.GroupId; + + return await this.eventService.CreateAsync(userId, eventEntity); + } + + + /// public async Task Search(ResourceSearchRequest query, int? currentUserId) { @@ -178,6 +570,26 @@ public async Task SendResourceForSearchAsync(SearchResourceRequestModel se } } + /// + /// The create catalogue search action async. + /// + /// + /// The search action request model. + /// + /// + /// The . + /// + public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchActionCatalogueModel) + { + var searchClickPayloadModel = this.mapper.Map(searchActionCatalogueModel); + searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.Catalogue; + + return await this.SendSearchEventClickAsync(searchClickPayloadModel, false); + } + /// /// Gets AllCatalogue search results from findwise api call. /// @@ -271,6 +683,128 @@ public async Task GetAutoSuggestionResultsAsync(string term } } + + /// + /// The AutoSuggestion Event action async. + /// + /// + /// The clic kPayload Model. + /// + /// + /// The . + /// + public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadModel clickPayloadModel) + { + clickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + clickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; + clickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; + clickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.findwiseConfig.CollectionIds.AutoSuggestion; + + return await this.SendAutoSuggestionEventClickAsync(clickPayloadModel); + } + + /// + /// Send search click payload. + /// + /// search click payload model. + /// isResource. + /// + /// The . + /// + private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) + { + var eventType = isResource ? "resource" : "catalog"; + + try + { + if (string.IsNullOrEmpty(this.findwiseConfig.UrlClickComponent)) + { + this.logger.LogWarning($"The UrlClickComponent is not configured. {eventType} click event not send to FindWise."); + } + else + { + var json = JsonConvert.SerializeObject(searchClickPayloadModel); + var base64EncodedString = BinaryFormatterHelper.Base64EncodeObject(json); + + var request = $"{this.findwiseConfig.UrlClickComponent}?payload={base64EncodedString}"; + + var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); + var response = await client.PostAsync(request, null).ConfigureAwait(false); + + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.logger.LogError($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); + throw new Exception("AccessDenied"); + } + else if (!response.IsSuccessStatusCode) + { + this.logger.LogError($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); + throw new Exception($"Click event save to FindWise failed for {eventType}: {json}"); + } + else + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + throw new Exception($"Click event save to FindWise failed for {eventType}: {searchClickPayloadModel.ClickTargetUrl} : {ex.Message}"); + } + } + + /// + /// Send auto suggestion click payload. + /// + /// search click payload model. + /// + /// The . + /// + private async Task SendAutoSuggestionEventClickAsync(AutoSuggestionClickPayloadModel clickPayloadModel) + { + try + { + if (string.IsNullOrEmpty(this.findwiseConfig.UrlAutoSuggestionClickComponent)) + { + this.logger.LogWarning($"The UrlClickComponent is not configured. Auto suggestion click event not send to FindWise."); + } + else + { + var json = JsonConvert.SerializeObject(clickPayloadModel); + var base64EncodedString = BinaryFormatterHelper.Base64EncodeObject(json); + + var request = $"{this.findwiseConfig.UrlAutoSuggestionClickComponent}?payload={base64EncodedString}"; + + var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); + var response = await client.PostAsync(request, null).ConfigureAwait(false); + + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.logger.LogError($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); + throw new Exception("AccessDenied"); + } + else if (!response.IsSuccessStatusCode) + { + this.logger.LogError($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} HTTP Status Code: {response.StatusCode}"); + throw new Exception($"Click event save to FindWise failed for Auto suggestion: {json}"); + } + else + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + throw new Exception($"Click event save to FindWise failed for Auto suggestion: {clickPayloadModel.ClickTargetUrl} : {ex.Message}"); + } + } + + private string EncodeSearchText(string searchText) { string specialSearchCharacters = this.findwiseConfig.SpecialSearchCharacters; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs new file mode 100644 index 000000000..7140d117b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs @@ -0,0 +1,938 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + using Newtonsoft.Json; + + /// + /// The user group service. + /// + public class UserGroupService : IUserGroupService + { + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// The catalogue service. + /// + private ICatalogueService catalogueService; + + /// + /// The user group repository. + /// + private IUserGroupRepository userGroupRepository; + + /// + /// The user user group repository. + /// + private IUserUserGroupRepository userUserGroupRepository; + + /// + /// The scope repository. + /// + private IScopeRepository scopeRepository; + + /// + /// The role user group repository. + /// + private IRoleUserGroupRepository roleUserGroupRepository; + + /// + /// The user group attribute repository. + /// + private IUserGroupAttributeRepository userGroupAttributeRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The catalogue service. + /// The user group repository. + /// The user - user group repository. + /// The scope repository. + /// The role - user group repository. + /// The user group attribute repository. + /// The mapper. + public UserGroupService( + ICatalogueService catalogueService, + IUserGroupRepository userGroupRepository, + IUserUserGroupRepository userUserGroupRepository, + IScopeRepository scopeRepository, + IRoleUserGroupRepository roleUserGroupRepository, + IUserGroupAttributeRepository userGroupAttributeRepository, + IMapper mapper) + { + this.catalogueService = catalogueService; + this.userGroupRepository = userGroupRepository; + this.userUserGroupRepository = userUserGroupRepository; + this.scopeRepository = scopeRepository; + this.roleUserGroupRepository = roleUserGroupRepository; + this.userGroupAttributeRepository = userGroupAttributeRepository; + this.mapper = mapper; + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await userGroupRepository.GetByIdAsync(id); + } + + /// + /// The get by id async. + /// + /// The id. + /// The include roles. + /// The . + public async Task GetByIdAsync(int id, bool includeRoles) + { + return await userGroupRepository.GetByIdAsync(id, includeRoles); + } + + /// + /// The create async. + /// + /// The user id. + /// The user group. + /// The . + public async Task CreateAsync(int userId, UserGroup userGroup) + { + var validationResult = await ValidateAsync(userGroup); + + if (validationResult.IsValid) + { + validationResult.CreatedId = await userGroupRepository.CreateAsync(userId, userGroup); + } + + return validationResult; + } + + /// + /// The delete async. + /// + /// The user id. + /// The user group id. + /// The . + public async Task DeleteAsync(int userId, int userGroupId) + { + var userGroup = await userGroupRepository.GetByIdAsync(userGroupId); + + if (userGroup == null) + { + return new LearningHubValidationResult(false, "User group not found."); + } + + try + { + await userGroupRepository.DeleteAsync(userId, userGroupId); + return new LearningHubValidationResult(true); + } + catch (Exception ex) + { + return new LearningHubValidationResult(false, $"Error: {ex.Message}"); + } + } + + /// + /// The validate async. + /// + /// The user group. + /// The . + public async Task ValidateAsync(UserGroup userGroup) + { + var userGroupValidator = new UserGroupValidator(); + var validatorResult = await userGroupValidator.ValidateAsync(userGroup); + + var retVal = new LearningHubValidationResult(validatorResult); + + if (retVal.IsValid) + { + var ug = await userGroupRepository.GetByNameAsync(userGroup.Name); + if (ug != null) + { + if (userGroup.IsNew() + || !userGroup.IsNew() && userGroup.Id != ug.Id) + { + var detail = string.Format("Name '{0}' is already in use", userGroup.Name); + retVal.Add(new LearningHubValidationResult(false, detail)); + } + } + } + + return retVal; + } + + /// + /// Returns a user group detail view model for the supplied id. + /// + /// The user group id. + /// The . + public async Task GetUserGroupAdminDetailByIdAsync(int id) + { + var userGroup = await userGroupRepository.GetByIdAsync(id, true); + + var model = mapper.Map(userGroup); + + return model; + } + + /// + /// Returns a list of role user group detail view models for the supplied user group id. + /// + /// The user group id. + /// The list of . + public async Task> GetUserGroupRoleDetailByUserGroupId(int userGroupId) + { + var vm = await roleUserGroupRepository.GetRoleUserGroupViewModelsByUserGroupId(userGroupId); + + return vm; + } + + /// + /// Returns a list of role user group detail view models for the supplied user id. + /// + /// The user group id. + /// The list of . + public async Task> GetRoleUserGroupDetailByUserId(int userId) + { + var vm = await roleUserGroupRepository.GetRoleUserGroupViewModelsByUserId(userId); + + return vm; + } + + /// + /// Create a user group. + /// + /// The user group admin detail view model. + /// The current user id. + /// The . + public async Task CreateUserGroupAsync(UserGroupAdminDetailViewModel userGroupAdminDetailViewModel, int currentUserId) + { + var ug = await userGroupRepository.GetByNameAsync(userGroupAdminDetailViewModel.Name); + if (ug != null) + { + return new LearningHubValidationResult(false, "User group name already exists"); + } + + var userGroup = new UserGroup(); + userGroup.Name = userGroupAdminDetailViewModel.Name; + userGroup.Description = userGroupAdminDetailViewModel.Description; + + var userGroupValidator = new UserGroupValidator(); + var validationResult = await userGroupValidator.ValidateAsync(userGroup); + var retVal = new LearningHubValidationResult(validationResult); + + if (retVal.IsValid) + { + retVal.CreatedId = await userGroupRepository.CreateAsync(currentUserId, userGroup); + } + + return retVal; + } + + /// + /// Updates a user group. + /// + /// The user group admin detail view model. + /// The current user id. + /// The . + public async Task UpdateUserGroupAsync(UserGroupAdminDetailViewModel userGroupAdminDetailViewModel, int currentUserId) + { + var userGroupValidator = new UserGroupValidator(); + + var userGroup = await userGroupRepository.GetByIdAsync(userGroupAdminDetailViewModel.Id); + + userGroup.Name = userGroupAdminDetailViewModel.Name; + userGroup.Description = userGroupAdminDetailViewModel.Description; + + var validationResult = await userGroupValidator.ValidateAsync(userGroup); + var retVal = new LearningHubValidationResult(validationResult); + + if (retVal.IsValid) + { + await userGroupRepository.UpdateAsync(currentUserId, userGroup); + } + + return retVal; + } + + /// + /// Adds a user group attribute. + /// + /// The user group attribute view model. + /// The current user id. + /// The . + public async Task AddUserGroupAttribute(UserGroupAttributeViewModel userGroupAttribute, int currentUserId) + { + if (!userGroupAttribute.IsNew()) + { + return new LearningHubValidationResult(false, $"Error - supplied user group attribute is not new."); + } + + var existing = await userGroupAttributeRepository.GetByUserGroupIdAttributeId(userGroupAttribute.UserGroupId, userGroupAttribute.AttributeId); + if (existing != null) + { + return new LearningHubValidationResult(false, $"Error - user group attribute already exists for the supplied identifiers."); + } + + try + { + var entity = mapper.Map(userGroupAttribute); + int id = await userGroupAttributeRepository.CreateAsync(currentUserId, entity); + + var retVal = new LearningHubValidationResult(true); + retVal.CreatedId = id; + return retVal; + } + catch (Exception ex) + { + return new LearningHubValidationResult(false, $"Error adding user group attribute: {ex.Message}"); + } + } + + /// + /// Removes a user group attribute. + /// + /// The user group attribute view model. + /// The current user id. + /// The . + public async Task DeleteUserGroupAttributeAsync(UserGroupAttributeViewModel userGroupAttribute, int currentUserId) + { + var entity = await userGroupAttributeRepository.GetByIdAsync(userGroupAttribute.UserGroupAttributeId); + + if (entity == null) + { + return new LearningHubValidationResult(false, "UserGroupAttribute not found."); + } + + entity.Deleted = true; + await userGroupAttributeRepository.UpdateAsync(currentUserId, entity); + + return new LearningHubValidationResult(true); + } + + /// + /// Adds a list of users to a user group. + /// + /// The user user group view model list. + /// The current user id. + /// The . + public async Task AddUserUserGroups(List userUserGroups, int currentUserId) + { + try + { + foreach (var userUserGroup in userUserGroups) + { + var userUserGroupslst = await userUserGroupRepository.GetByUserIdandUserGroupIdAsync(userUserGroup.UserId, userUserGroup.UserGroupId); + if (userUserGroupslst == null) + { + var entity = mapper.Map(userUserGroup); + await userUserGroupRepository.CreateAsync(currentUserId, entity); + } + } + } + catch (Exception ex) + { + return new LearningHubValidationResult(false, $"Error adding users to user group: {ex.Message}"); + } + + return new LearningHubValidationResult(true); + } + + /// + /// Adds a list of role user groups. + /// Creates a new Scope entry if necessary. + /// + /// The role user group view model list. + /// The current user id. + /// The . + public async Task AddRoleUserGroups(List roleUserGroups, int currentUserId) + { + var newRoleUserGroups = new List(); + try + { + foreach (var roleUserGroup in roleUserGroups) + { + // Cater for Catalogue Scope Type. + // Create new Scope if necessary. + // Reture validation failure if any supplied RoleUserGroup already exists. + if (roleUserGroup.ScopeType == ScopeTypeEnum.Catalogue) + { + int scopeId; + var scope = await scopeRepository.GetByCatalogueNodeIdAsync(roleUserGroup.CatalogueNodeId); + if (scope == null) + { + scope = new Scope() { ScopeType = ScopeTypeEnum.Catalogue, CatalogueNodeId = roleUserGroup.CatalogueNodeId }; + scopeId = await scopeRepository.CreateAsync(currentUserId, scope); + } + else + { + scopeId = scope.Id; + + var rug = await roleUserGroupRepository.GetByRoleIdUserGroupIdScopeIdAsync(roleUserGroup.RoleId, roleUserGroup.UserGroupId, scopeId); + if (rug != null) + { + return new LearningHubValidationResult(false, $"RoleUserGroup with Catalogue ScopeType already exists: RoleId={roleUserGroup.RoleId}, UserGroupId={roleUserGroup.UserGroupId}, ScopeId={scopeId}"); + } + } + + newRoleUserGroups.Add(new RoleUserGroup() + { + RoleId = roleUserGroup.RoleId, + UserGroupId = roleUserGroup.UserGroupId, + ScopeId = scopeId, + }); + } + } + + foreach (var roleUserGroup in newRoleUserGroups) + { + await roleUserGroupRepository.CreateAsync(currentUserId, roleUserGroup); + } + } + catch (Exception ex) + { + return new LearningHubValidationResult(false, $"Error adding role user group: {ex.Message}"); + } + + return new LearningHubValidationResult(true); + } + + /// + /// Removes user from a user group. + /// + /// The user user group view model. + /// The current user id. + /// The . + public async Task DeleteUserUserGroupAsync(UserUserGroupViewModel userUserGroupViewModel, int currentUserId) + { + var userUserGroup = await userUserGroupRepository.GetByIdAsync(userUserGroupViewModel.Id); + + if (userUserGroup == null) + { + return new LearningHubValidationResult(false, "User user group not found."); + } + + userUserGroup.Deleted = true; + await userUserGroupRepository.UpdateAsync(currentUserId, userUserGroup); + + return new LearningHubValidationResult(true); + } + + /// + /// Removes a role - user group. + /// + /// The role user group view model. + /// The current user id. + /// The . + public async Task DeleteRoleUserGroupAsync(RoleUserGroupUpdateViewModel roleUserGroupViewModel, int currentUserId) + { + var roleUserGroup = await roleUserGroupRepository.GetByIdAsync(roleUserGroupViewModel.Id); + + if (roleUserGroup == null) + { + return new LearningHubValidationResult(false, "Role - UserGroup association not found."); + } + + // Do not allow delete of "default restricted access user group" in a Restricted Catalogue + if (roleUserGroup.RoleId == (int)RoleEnum.Reader + && + roleUserGroup.UserGroup.UserGroupAttribute.Any(uga => uga.AttributeId == (int)AttributeEnum.RestrictedAccess) + && + roleUserGroup.Scope.ScopeType == ScopeTypeEnum.Catalogue) + { + var catalogue = catalogueService.GetBasicCatalogue(roleUserGroup.Scope.CatalogueNodeId.Value); + if (catalogue.RestrictedAccess) + { + return new LearningHubValidationResult(false, "Cannot delete the default Restricted Access User Group in a Restricted Catalogue"); + } + } + + // Ensure that at least one Local Admin group exists for a Restricted Catalogue + if (roleUserGroup.RoleId == (int)RoleEnum.LocalAdmin + && + roleUserGroup.Scope.ScopeType == ScopeTypeEnum.Catalogue) + { + var catalogue = catalogueService.GetBasicCatalogue(roleUserGroup.Scope.CatalogueNodeId.Value); + if (catalogue.RestrictedAccess && !catalogue.Hidden) + { + var roleUserGroups = await roleUserGroupRepository.GetByRoleIdCatalogueId(roleUserGroup.RoleId, catalogue.NodeId); + if (!roleUserGroups.Any(rug => rug.Id != roleUserGroupViewModel.Id)) + { + return new LearningHubValidationResult(false, "A Restricted Catalogue that is visible must have at least one Local Admin User Group."); + } + } + } + + roleUserGroup.Deleted = true; + await roleUserGroupRepository.UpdateAsync(currentUserId, roleUserGroup); + + return new LearningHubValidationResult(true); + } + + /// + /// Returns a list of "role - user group" info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + public async Task> GetRoleUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = "") + { + var presetFilterCriteria = JsonConvert.DeserializeObject>(presetFilter); + var filterCriteria = JsonConvert.DeserializeObject>(filter); + + PagedResultSet result = new PagedResultSet(); + + var items = roleUserGroupRepository.GetAll(); + + items = this.PresetFilterRoleUserGroupItems(items, presetFilterCriteria); + items = this.FilterRoleUserGroupItems(items, filterCriteria); + + result.TotalItemCount = items.Count(); + + items = this.OrderRoleUserGroupItems(items, sortColumn, sortDirection); + items = items.Skip((page - 1) * pageSize).Take(pageSize); + + result.Items = await mapper.ProjectTo(items).ToListAsync(); + + return result; + } + + /// + /// Returns a list of "user - user group" info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + public async Task> GetUserUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = "") + { + var presetFilterCriteria = JsonConvert.DeserializeObject>(presetFilter); + var filterCriteria = JsonConvert.DeserializeObject>(filter); + + PagedResultSet result = new PagedResultSet(); + + var items = userUserGroupRepository.GetAll(); + + items = this.PresetFilterUserUserGroupItems(items, presetFilterCriteria); + items = this.FilterUserUserGroupItems(items, filterCriteria); + + result.TotalItemCount = items.Count(); + + items = this.OrderUserUserGroupItems(items, sortColumn, sortDirection); + items = items.Skip((page - 1) * pageSize).Take(pageSize); + + result.Items = await mapper.ProjectTo(items).ToListAsync(); + + return result; + } + + /// + /// Returns a list of basic user group info - filtered, sorted and paged as required. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The preset filter. + /// The filter. + /// The . + public async Task> GetUserGroupAdminBasicPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = "") + { + var presetFilterCriteria = JsonConvert.DeserializeObject>(presetFilter); + var filterCriteria = JsonConvert.DeserializeObject>(filter); + + PagedResultSet result = new PagedResultSet(); + + var items = userGroupRepository.GetAll(); + + items = this.PresetFilterUserGroupItems(items, presetFilterCriteria); + items = this.FilterUserGroupItems(items, filterCriteria); + + result.TotalItemCount = items.Count(); + + items = this.OrderUserGroupItems(items, sortColumn, sortDirection); + + items = items.Skip((page - 1) * pageSize).Take(pageSize); + + result.Items = await mapper.ProjectTo(items).ToListAsync(); + + return result; + } + + /// + /// Apply preset filter to the items for user user group search. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable PresetFilterUserUserGroupItems(IQueryable items, List presetFilterCriteria) + { + if (presetFilterCriteria == null || presetFilterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in presetFilterCriteria) + { + switch (filter.Column.ToLower()) + { + case "userid": + items = items.Where(x => x.UserId == int.Parse(filter.Value)); + break; + case "usergroupid": + items = items.Where(x => x.UserGroupId == int.Parse(filter.Value)); + break; + default: + break; + } + } + + return items; + } + + /// + /// Filter the items for user user group search. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable FilterUserUserGroupItems(IQueryable items, List filterCriteria) + { + if (filterCriteria == null || filterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in filterCriteria) + { + switch (filter.Column.ToLower()) + { + case "userid": + int enteredId = 0; + int.TryParse(filter.Value, out enteredId); + items = items.Where(x => x.UserId == enteredId); + break; + case "usergroupid": + items = items.Where(x => x.UserGroupId == int.Parse(filter.Value)); + break; + case "usergroupname": + items = items.Where(x => x.UserGroup.Name.Contains(filter.Value)); + break; + case "username": + items = items.Where(x => x.User.UserName.Contains(filter.Value)); + break; + default: + break; + } + } + + return items; + } + + /// + /// Filter the items for role user group search. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable FilterRoleUserGroupItems(IQueryable items, List filterCriteria) + { + if (filterCriteria == null || filterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in filterCriteria) + { + switch (filter.Column.ToLower()) + { + case "id": + int enteredId = 0; + int.TryParse(filter.Value, out enteredId); + items = items.Where(x => x.UserGroupId == enteredId); + break; + case "name": + items = items.Where(x => x.UserGroup.Name.Contains(filter.Value)); + break; + default: + break; + } + } + + return items; + } + + /// + /// Order the items for user - user group search. + /// + /// The items. + /// The sort column. + /// The sort direction. + /// The . + private IQueryable OrderUserUserGroupItems(IQueryable items, string sortColumn, string sortDirection) + { + switch (sortColumn.ToLower()) + { + case "username": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.User.UserName); + } + else + { + items = items.OrderBy(x => x.User.UserName); + } + + break; + default: + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.UserId); + } + else + { + items = items.OrderBy(x => x.UserId); + } + + break; + } + + return items; + } + + /// + /// Order the items for role - user group. + /// + /// The items. + /// The sort column. + /// The sort direction. + /// The . + private IQueryable OrderRoleUserGroupItems(IQueryable items, string sortColumn, string sortDirection) + { + switch (sortColumn.ToLower()) + { + case "usergroupname": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.UserGroup.Name); + } + else + { + items = items.OrderBy(x => x.UserGroup.Name); + } + + break; + default: + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.UserGroupId); + } + else + { + items = items.OrderBy(x => x.UserGroupId); + } + + break; + } + + return items; + } + + /// + /// Preset filter the items for user group search. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable PresetFilterUserGroupItems(IQueryable items, List presetFilterCriteria) + { + if (presetFilterCriteria == null || presetFilterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in presetFilterCriteria) + { + switch (filter.Column.ToLower()) + { + case "userid": + int enteredId = 0; + int.TryParse(filter.Value, out enteredId); + items = items.Where(x => x.Id == enteredId); + break; + case "userid_exclude": + items = items.Where(x => !x.UserUserGroup.Any(uug => uug.UserId == int.Parse(filter.Value))); + break; + case "cataloguenodeid": + var roleFilter = presetFilterCriteria.Where(f => f.Column.ToLower() == "roleid").FirstOrDefault(); + int catalogueNodeId = 0; + int.TryParse(filter.Value, out catalogueNodeId); + if (roleFilter == null) + { + items = items.Where(x => x.RoleUserGroup.Any(rug => rug.Scope.CatalogueNodeId == catalogueNodeId)); + } + else + { + int roleId = 0; + int.TryParse(roleFilter.Value, out roleId); + items = items.Where(x => x.RoleUserGroup.Any(rug => rug.RoleId == roleId && rug.Scope.CatalogueNodeId == catalogueNodeId)); + } + + break; + case "cataloguenodeid_exclude": + var excludeRoleFilter = presetFilterCriteria.Where(f => f.Column.ToLower() == "roleid_exclude").FirstOrDefault(); + int excludeCatalogueNodeId = 0; + int.TryParse(filter.Value, out excludeCatalogueNodeId); + if (excludeRoleFilter == null) + { + items = items.Where(x => x.RoleUserGroup.Any(rug => rug.Scope.CatalogueNodeId == excludeCatalogueNodeId)); + } + else + { + int roleId = 0; + int.TryParse(excludeRoleFilter.Value, out roleId); + items = items.Where(x => !x.RoleUserGroup.Any(rug => rug.RoleId == roleId && rug.Scope.CatalogueNodeId == excludeCatalogueNodeId + || rug.RoleId == roleId && rug.Scope.ScopeType == ScopeTypeEnum.Catalogue && !rug.Scope.CatalogueNodeId.HasValue)); + } + + break; + default: + break; + } + } + + return items; + } + + /// + /// Preset filter the items for role user group. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable PresetFilterRoleUserGroupItems(IQueryable items, List presetFilterCriteria) + { + if (presetFilterCriteria == null || presetFilterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in presetFilterCriteria) + { + switch (filter.Column.ToLower()) + { + case "roleid": + int roleId = 0; + int.TryParse(filter.Value, out roleId); + items = items.Where(x => x.RoleId == roleId); + break; + case "cataloguenodeid": + int catalogueNodeId = 0; + int.TryParse(filter.Value, out catalogueNodeId); + items = items.Where(x => x.Scope.CatalogueNodeId == catalogueNodeId); + + break; + default: + break; + } + } + + return items; + } + + /// + /// Filter the items for user group search. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable FilterUserGroupItems(IQueryable items, List filterCriteria) + { + if (filterCriteria == null || filterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in filterCriteria) + { + switch (filter.Column.ToLower()) + { + case "id": + int enteredId = 0; + int.TryParse(filter.Value, out enteredId); + items = items.Where(x => x.Id == enteredId); + break; + case "usergroupname": + case "name": + items = items.Where(x => x.Name.Contains(filter.Value)); + break; + default: + break; + } + } + + return items; + } + + /// + /// Order the items for user group search. + /// + /// The items. + /// The sort column. + /// The sort direction. + /// The . + private IQueryable OrderUserGroupItems(IQueryable items, string sortColumn, string sortDirection) + { + switch (sortColumn.ToLower()) + { + case "name": + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.Name); + } + else + { + items = items.OrderBy(x => x.Name); + } + + break; + default: + if (sortDirection == "D") + { + items = items.OrderByDescending(x => x.Id); + } + else + { + items = items.OrderBy(x => x.Id); + } + + break; + } + + return items; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserLearningRecordService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserLearningRecordService.cs new file mode 100644 index 000000000..ca72e9452 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserLearningRecordService.cs @@ -0,0 +1,129 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + + /// + /// The rating service. + /// + public class UserLearningRecordService : IUserLearningRecordService + { + /// + /// The resourceActivityRepository. + /// + private readonly IResourceActivityRepository resourceActivityRepository; + private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; + + /// + /// The mediaResourcePlayedSegmentRepository. + /// + private readonly IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository; + + /// + /// The assessment resource activity repository. + /// + private readonly IAssessmentResourceActivityRepository assessmentResourceActivityRepository; + + /// + /// The assessment resource activity interaction repository. + /// + private readonly IAssessmentResourceActivityInteractionRepository assessmentResourceActivityInteractionRepository; + private readonly IMyLearningService myLearningService; + + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// The settings. + /// + private readonly IOptions settings; + + /// + /// Initializes a new instance of the class. + /// + /// The resource activity repository. + /// The catalogue node repository. + /// The mediaResourcePlayedSegmentRepository. + /// The assessmentResourceActivityRepository. + /// The assessmentResourceActivityInteractionRepository. + /// The myLearningService. + /// The mapper. + /// The settings. + public UserLearningRecordService( + IResourceActivityRepository resourceActivityRepository, + IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository, + IAssessmentResourceActivityRepository assessmentResourceActivityRepository, + IAssessmentResourceActivityInteractionRepository assessmentResourceActivityInteractionRepository, + ICatalogueNodeVersionRepository catalogueNodeVersionRepository, + IMyLearningService myLearningService, + IMapper mapper, + IOptions settings) + { + this.resourceActivityRepository = resourceActivityRepository; + this.mediaResourcePlayedSegmentRepository = mediaResourcePlayedSegmentRepository; + this.assessmentResourceActivityRepository = assessmentResourceActivityRepository; + this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; + this.assessmentResourceActivityInteractionRepository = assessmentResourceActivityInteractionRepository; + this.myLearningService = myLearningService; + this.mapper = mapper; + this.settings = settings; + } + + /// + /// GetUserLearningRecordsAsync. + /// + /// page. + /// pageSize. + /// sortColumn. + /// sortDirection. + /// presetFilter. + /// filter. + /// The . + public async Task> GetUserLearningRecordsAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = "") + { + try + { + PagedResultSet result = new PagedResultSet(); + var presetFilterCriteria = JsonConvert.DeserializeObject>(presetFilter); + var userIdFilter = presetFilterCriteria.Where(f => f.Column == "userid").FirstOrDefault(); + + MyLearningRequestModel requestModel = new MyLearningRequestModel(); + requestModel.Skip = (page - 1) * pageSize; + requestModel.Take = pageSize; + var activityQuery = resourceActivityRepository.GetByUserIdFromSP(int.Parse(userIdFilter.Value), requestModel, settings.Value.DetailedMediaActivityRecordingStartDate).Result.OrderByDescending(r => r.ActivityStart).DistinctBy(l => l.Id); + MyLearningDetailedViewModel viewModel = new MyLearningDetailedViewModel() + { + TotalCount = resourceActivityRepository.GetTotalCount(int.Parse(userIdFilter.Value), requestModel, settings.Value.DetailedMediaActivityRecordingStartDate), + }; + + var activityEntities = activityQuery.ToList(); + + viewModel.Activities = await myLearningService.PopulateMyLearningDetailedItemViewModels(activityEntities, int.Parse(userIdFilter.Value)); + if (userIdFilter != null) + { + result.Items = viewModel.Activities; /*this.mapper.Map>(viewModel.Activities);*/ + result.TotalItemCount = viewModel.TotalCount; + } + + return result; + } + catch (Exception ex) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProviderService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProviderService.cs new file mode 100644 index 000000000..b04fa9687 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProviderService.cs @@ -0,0 +1,52 @@ +namespace LearningHub.Nhs.Services +{ + using System; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Provider; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + + /// + /// The user provider service. + /// + public class UserProviderService : IUserProviderService + { + /// + /// The user provider repository. + /// + private readonly IUserProviderRepository userProviderRepository; + + /// + /// mapper. + /// + private readonly IMapper mapper; + + /// + /// Initializes a new instance of the class. + /// + /// The user provider repository. + /// The mapper. + public UserProviderService(IUserProviderRepository userProviderRepository, IMapper mapper) + { + this.userProviderRepository = userProviderRepository; + this.mapper = mapper; + } + + /// + public async Task UpdateUserProviderAsync(UserProviderUpdateViewModel userProviderUpdateModel) + { + try + { + await this.userProviderRepository.UpdateUserProviderAsync(userProviderUpdateModel); + } + catch (Exception ex) + { + return new LearningHubValidationResult(false, $"Error updating provided by permission to user: {ex.Message}"); + } + + return new LearningHubValidationResult(true); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index f620c101b..86406d0e5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -8,6 +8,7 @@ namespace LearningHub.Nhs.OpenApi.Services using LearningHub.Nhs.OpenApi.Services.Services; using LearningHub.Nhs.OpenApi.Services.Services.Findwise; using LearningHub.Nhs.OpenApi.Services.Services.Messaging; + using LearningHub.Nhs.Services; using Microsoft.Extensions.DependencyInjection; /// @@ -32,7 +33,15 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -51,7 +60,8 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddScoped(); + services.AddScoped(); } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs index 396ad050d..e9eba00aa 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs @@ -85,6 +85,9 @@ public class ResourceServiceTests private readonly Mock resourceVersionProviderRepository; private readonly Mock resourceVersionAuthorRepository; private readonly Mock fileChunkDetailRepository; + private readonly Mock resourceSyncRepository; + private readonly Mock resourceVersionEventRepository; + private readonly int currentUserId; public ResourceServiceTests() @@ -144,8 +147,10 @@ public ResourceServiceTests() this.resourceVersionProviderRepository = new Mock(); this.resourceVersionAuthorRepository = new Mock(); this.fileChunkDetailRepository = new Mock(); + this.resourceSyncRepository = new Mock(); + this.resourceVersionEventRepository = new Mock(); this.logger = new Mock>(); - this.resourceService = new ResourceService(this.learningHubService.Object, this.fileTypeService.Object, this.blockCollectionRepository.Object, this.internalSystemService.Object, this.resourceVersionAuthorRepository.Object, this.fileChunkDetailRepository.Object, this.queueCommunicatorService.Object, this.resourceRepository.Object, this.resourceVersionProviderRepository.Object, this.providerService.Object, this.articleResourceVersionFileRepository.Object, this.publicationRepository.Object, this.migrationSourceRepository.Object, this.questionBlockRepository.Object, this.videoRepository.Object, this.wholeSlideImageRepository.Object, this.embeddedResourceVersionRepository.Object, this.equipmentResourceVersionRepository.Object, this.imageResourceVersionRepository.Object, this.bookmarkRepository.Object, this.assessmentResourceActivityMatchQuestionRepository.Object, this.resourceVersionKeywordRepository.Object, this.resourceVersionValidationResultRepository.Object, this.logger.Object, this.webLinkResourceVersionRepository.Object, this.caseResourceVersionRepository.Object, this.scormResourceVersionRepository.Object, this.genericFileResourceVersionRepository.Object, this.resourceVersionRepository.Object, this.htmlResourceVersionRepository.Object, this.mapper.Object, this.fileRepository.Object, this.azureConfig.Object, this.learningHubConfig.Object, this.userProfileService.Object, this.resourceVersionFlagRepository.Object, this.articleResourceVersionRepository.Object, this.audioResourceVersionRepository.Object, this.videoResourceVersionRepository.Object, this.assessmentResourceVersionRepository.Object, this.resourceLicenceRepository.Object, this.resourceReferenceRepository.Object, this.resourceVersionUserAcceptanceRepository.Object, this.catalogueNodeVersionRepository.Object, this.cachingService.Object, this.searchService.Object, this.catalogueService.Object, this.nodeResourceRepository.Object, this.nodePathRepository.Object, this.userService.Object, this.nodeRepository.Object,this.resourceSyncService.Object, this.dbContext.Object.As()); + this.resourceService = new ResourceService(this.learningHubService.Object, this.fileTypeService.Object, this.blockCollectionRepository.Object, this.internalSystemService.Object, this.resourceVersionAuthorRepository.Object, this.fileChunkDetailRepository.Object, this.queueCommunicatorService.Object, this.resourceRepository.Object, this.resourceVersionProviderRepository.Object, this.providerService.Object, this.articleResourceVersionFileRepository.Object, this.publicationRepository.Object, this.migrationSourceRepository.Object, this.questionBlockRepository.Object, this.videoRepository.Object, this.wholeSlideImageRepository.Object, this.embeddedResourceVersionRepository.Object, this.equipmentResourceVersionRepository.Object, this.imageResourceVersionRepository.Object, this.bookmarkRepository.Object, this.assessmentResourceActivityMatchQuestionRepository.Object, this.resourceVersionKeywordRepository.Object, this.resourceVersionValidationResultRepository.Object, this.logger.Object, this.webLinkResourceVersionRepository.Object, this.caseResourceVersionRepository.Object, this.scormResourceVersionRepository.Object, this.genericFileResourceVersionRepository.Object, this.resourceVersionRepository.Object, this.htmlResourceVersionRepository.Object, this.mapper.Object, this.fileRepository.Object, this.azureConfig.Object, this.learningHubConfig.Object, this.userProfileService.Object, this.resourceVersionFlagRepository.Object, this.articleResourceVersionRepository.Object, this.audioResourceVersionRepository.Object, this.videoResourceVersionRepository.Object, this.assessmentResourceVersionRepository.Object, this.resourceLicenceRepository.Object, this.resourceReferenceRepository.Object, this.resourceVersionUserAcceptanceRepository.Object, this.catalogueNodeVersionRepository.Object, this.cachingService.Object, this.searchService.Object, this.catalogueService.Object, this.nodeResourceRepository.Object, this.nodePathRepository.Object, this.userService.Object, this.nodeRepository.Object,this.resourceSyncService.Object, this.resourceSyncRepository.Object, this.resourceVersionEventRepository.Object, this.dbContext.Object.As()); } private List ResourceActivityDTOList => new List() diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs index ca7b65d49..d8d0568b2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs @@ -3,6 +3,7 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using AutoMapper; using FizzWare.NBuilder; using FluentAssertions; using FluentAssertions.Execution; @@ -32,10 +33,11 @@ public class SearchServiceTests private readonly Mock learningHubService; private readonly Mock resourceRepository; private readonly Mock resourceService; + private readonly Mock eventService; private readonly SearchService searchService; private readonly Mock> findwiseConfig; private Mock> mockLogger; - + private readonly Mock mapper; public SearchServiceTests() { @@ -43,14 +45,19 @@ public SearchServiceTests() this.learningHubService = new Mock(); this.resourceRepository = new Mock(); this.resourceService = new Mock(); + this.eventService = new Mock(); + this.mapper = new Mock(); + this.findwiseConfig = new Mock>(); this.mockLogger = new Mock>(); this.searchService = new SearchService( this.learningHubService.Object, + this.eventService.Object, this.findwiseClient.Object, this.findwiseConfig.Object, this.resourceRepository.Object, - this.mockLogger.Object); + this.mockLogger.Object, + this.mapper.Object); } public static IEnumerable TestFindwiseResultModel => new[] diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs index 4286352a9..c0bb7d4a2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs @@ -36,6 +36,20 @@ public async Task> GetAllByParent() return await this.bookmarkService.GetAllByParent(this.TokenWithoutBearer); } + /// + /// The GetAllByParent. + /// + /// The parentId. + /// The all. + /// The . + [HttpGet] + [Route("GetAllByParent/{parentId?}")] + public async Task GetAllByParent(int? parentId, bool? all = false) + { + var bookmarks = await this.bookmarkService.GetAllByParent(this.CurrentUserId.GetValueOrDefault(), parentId, all); + return this.Ok(bookmarks); + } + /// /// The Create. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs new file mode 100644 index 000000000..f7c03834e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs @@ -0,0 +1,331 @@ +namespace LearningHub.Nhs.Api.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Content; + using LearningHub.Nhs.Models.Validation; + using LearningHub.NHS.OpenAPI.Controllers; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + /// + /// The ContentController. + /// + [Authorize(Policy = "AuthorizeOrCallFromLH")] + [Route("api/content")] + [ApiController] + public class ContentController : OpenApiControllerBase + { + /// + /// Defines the pageService. + /// + private readonly IPageService pageService; + + /// + /// Initializes a new instance of the class. + /// + /// pageService. + public ContentController(IPageService pageService) + { + this.pageService = pageService; + } + + /// + /// Gets pages. + /// + /// IActionResult. + [HttpGet] + [Route("pages")] + public async Task GetPagesAsync() + { + var response = await this.pageService.GetPagesAsync(); + return this.Ok(response); + } + + /// + /// Gets page. + /// + /// The id. + /// The published only. + /// Return page sections seen in preview mode. + /// IActionResult. + [HttpGet] + [Route("page/{id}")] + public async Task GetPageByIdAsync(int id, bool publishedOnly = false, bool preview = false) + { + var response = await this.pageService.GetPageByIdAsync(id, publishedOnly: publishedOnly, preview: preview); + return this.Ok(response); + } + + /// + /// Gets page. + /// + /// The id. + /// IActionResult. + [HttpGet] + [Route("page-all/{id}")] + public async Task GetPageWithAllSectionsByIdAsync(int id) + { + var response = await this.pageService.GetPageByIdAsync(id, true); + return this.Ok(response); + } + + /// + /// Gets page. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("discard/{pageId}")] + public async Task DiscardAsync(int pageId) + { + await this.pageService.DiscardAsync(pageId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Gets page. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("publish/{pageId}")] + public async Task PublishAsync(int pageId) + { + await this.pageService.PublishAsync(pageId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Clone page section. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("clone/{pageSectionId}")] + public async Task CloneAsync(int pageSectionId) + { + await this.pageService.CloneAsync(pageSectionId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Clone page section. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("change-order")] + public async Task ChangeOrderAsync([FromBody] UpdatePageSectionOrderModel requestViewModel) + { + await this.pageService.ChangeOrderAsync(requestViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Hide page section. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("hide/{pageSectionId}")] + public async Task HideAsync(int pageSectionId) + { + await this.pageService.HideAsync(pageSectionId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Unhide page section. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("unhide/{pageSectionId}")] + public async Task UnHideAsync(int pageSectionId) + { + await this.pageService.UnHideAsync(pageSectionId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// Unhide page section. + /// + /// The id. + /// IActionResult. + [HttpPut] + [Route("delete/{pageSectionId}")] + public async Task DeleteAsync(int pageSectionId) + { + await this.pageService.DeleteAsync(pageSectionId, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// The GetPageSectionById. + /// + /// The id. + /// The . + [HttpGet("page-section/{id}")] + public async Task GetPageSectionByDetailIdAsync(int id) + { + var responseViewModel = await this.pageService.GetPageSectionByIdAsync(id); + return this.Ok(responseViewModel); + } + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + [HttpGet("page-section-detail/{id}")] + public async Task GetPageSectionDetailByIdAsync(int id) + { + var responseViewModel = await this.pageService.GetPageSectionDetailImageAssetByIdAsync(id); + return this.Ok(responseViewModel); + } + + /// + /// The GetPageSectionDetailById. + /// + /// The id. + /// The . + [HttpGet("editable-page-section-detail/{id}")] + public async Task GetEditablePageSectionDetailByPageSectionIdAsync(int id) + { + var responseViewModel = await this.pageService.GetEditablePageSectionDetailByPageSectionIdAsync(id, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(responseViewModel); + } + + /// + /// The GetPageSectionDetailVideoById. + /// + /// The id. + /// The . + [HttpGet("page-section-detail-video/{id}")] + public async Task GetPageSectionDetailVideoAssetByIdAsync(int id) + { + var responseViewModel = await this.pageService.GetPageSectionDetailVideoAssetByIdAsync(id); + return this.Ok(responseViewModel); + } + + /// + /// The CreatePageSectionAsync. + /// + /// requestViewModel. + /// The . + [HttpPost("create-page-section")] + public async Task CreatePageSectionAsync(PageSectionViewModel requestViewModel) + { + var pageSectionId = await this.pageService.CreatePageSectionAsync(requestViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(pageSectionId); + } + + /// + /// Update page image section detail. + /// + /// The pageId. + /// The update model. + /// The . + [HttpPost("page-image-section-detail/{pageid}")] + public async Task UpdatePageImageSectionDetailAsync(int pageId, PageImageSectionUpdateViewModel model) + { + await this.pageService.UpdatePageImageSectionDetailAsync(pageId, model, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + + /// + /// The save video asset async. + /// + /// The requestViewModel. + /// The . + [HttpPost] + [Route("save-video-asset")] + public async Task SaveVideoAssetAsync(FileCreateRequestViewModel requestViewModel) + { + var vr = await this.pageService.SaveVideoAssetAsync(requestViewModel, this.CurrentUserId.GetValueOrDefault()); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// The save file details for a video asset attribute async. + /// + /// The requestViewModel. + /// The . + [HttpPost] + [Route("save-attribute-file")] + public async Task SaveAttributeFileDetails(FileCreateRequestViewModel requestViewModel) + { + var vr = await this.pageService.SaveAttributeFileDetails(requestViewModel, this.CurrentUserId.GetValueOrDefault()); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// The UpdateVideoAssetAsync. + /// + /// videoAssetStateViewModel. + /// A representing the result of the asynchronous operation. + [HttpPost] + [Route("update-video-asset-state")] + public async Task UpdateVideoAssetStateAsync(UpdateVideoAssetStateViewModel videoAssetStateViewModel) + { + await this.pageService.UpdateVideoAssetStateAsync(videoAssetStateViewModel); + return this.Ok(new ApiResponse(true, new LearningHubValidationResult { IsValid = true })); + } + + /// + /// The UpdateVideoAssetManifestDetailsAsync. + /// + /// The viewModel. + /// The . + [HttpPost] + [Route("update-video-asset-manifest")] + public async Task UpdateVideoAssetManifestDetailsAsync(UpdateVideoAssetManifestRequestViewModel viewModel) + { + await this.pageService.UpdateVideoAssetManifestDetailsAsync(viewModel); + return this.Ok(new ApiResponse(true, new LearningHubValidationResult { IsValid = true })); + } + + /// + /// The UpdateVideoAssetAsync. + /// + /// The viewModel. + /// The . + [HttpPost] + [Route("update-video-asset")] + public async Task UpdateVideoAssetAsync(VideoAssetViewModel viewModel) + { + viewModel.AmendUserId = this.CurrentUserId.GetValueOrDefault(); + await this.pageService.UpdateVideoAssetAsync(viewModel); + return this.Ok(new ApiResponse(true, new LearningHubValidationResult { IsValid = true })); + } + + /// + /// save page section details. + /// + /// The requestViewModel. + /// The . + [HttpPut] + [Route("update-page-section-detail")] + public async Task UpdatePageSectionDetailsAsync(PageSectionDetailViewModel requestViewModel) + { + await this.pageService.UpdatePageSectionDetailsAsync(requestViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs new file mode 100644 index 000000000..29e21f5e7 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs @@ -0,0 +1,72 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + + /// + /// The DashboardController. + /// + [Authorize(Policy = "AuthorizeOrCallFromLH")] + [Route("api/dashboard")] + [ApiController] + public class DashboardController : OpenApiControllerBase + { + private readonly IDashboardService dashboardService; + + /// + /// Initializes a new instance of the class. + /// + /// userService. + /// dashboardService. + /// The logger. + public DashboardController(IUserService userService, IDashboardService dashboardService, ILogger logger) + { + this.dashboardService = dashboardService; + } + + /// + /// Gets resources. + /// + /// The dashboard type. + /// The page Number. + /// IActionResult. + [HttpGet] + [Route("resources/{dashboardType}/{pageNumber}")] + public async Task GetResources(string dashboardType, int pageNumber = 1) + { + var response = await dashboardService.GetResources(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(response); + } + + /// + /// Gets Catalogues. + /// + /// The dashboard type. + /// The page Number. + /// IActionResult. + [HttpGet] + [Route("catalogues/{dashboardType}/{pageNumber}")] + public async Task GetCatalogues(string dashboardType, int pageNumber = 1) + { + var response = await dashboardService.GetCatalogues(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(response); + } + + /// + /// Gets Catalogues. + /// + /// The dashboard type. + /// The page Number. + /// IActionResult. + [HttpGet] + [Route("myaccesslearning/{dashboardType}/{pageNumber}")] + public async Task GetMyAccessLearnings(string dashboardType, int pageNumber = 1) + { + var response = await dashboardService.GetMyAccessLearnings(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(response); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs new file mode 100644 index 000000000..023faa590 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs @@ -0,0 +1,86 @@ +namespace LearningHub.Nhs.Api.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.NHS.OpenAPI.Controllers; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + + /// + /// MyLearning operations. + /// + [Route("MyLearning")] + [ApiController] + [Authorize] + public class MyLearningController : OpenApiControllerBase + { + /// + /// The MyLearning service. + /// + private readonly IMyLearningService myLearningService; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The user service. + /// + /// + /// The myLearning service. + /// + /// The logger. + public MyLearningController( + IUserService userService, + IMyLearningService myLearningService, + ILogger logger) + { + this.myLearningService = myLearningService; + } + + /// + /// Gets the activity records for the detailed activity tab of My Learning screen. + /// + /// The request model. + /// The . + [HttpPost] + [Route("GetActivityDetailed")] + public async Task GetActivityDetailed([FromBody] MyLearningRequestModel requestModel) + { + var activityModel = await this.myLearningService.GetActivityDetailed(this.CurrentUserId.GetValueOrDefault(), requestModel); + return this.Ok(activityModel); + } + + /// + /// Gets the played segment data for the progress modal in My Learning screen. + /// + /// The resourceId. + /// The majorVersion. + /// The . + [HttpGet] + [Route("GetPlayedSegments/{resourceId}/{majorVersion}")] + public async Task GetPlayedSegments(int resourceId, int majorVersion) + { + var segments = await this.myLearningService.GetPlayedSegments(this.CurrentUserId.GetValueOrDefault(), resourceId, majorVersion); + return this.Ok(segments); + } + + /// + /// Gets the resource certificate details of a resource reference. + /// + /// The resourceReferenceId. + /// The majorVersion. + /// The minorVersion. + /// The userId. + /// The . + [HttpGet] + [Route("GetResourceCertificateDetails/{resourceReferenceId}")] + [Route("GetResourceCertificateDetails/{resourceReferenceId}/{majorVersion}/{minorVersion}/{userId}")] + public async Task GetResourceCertificateDetails(int resourceReferenceId, int majorVersion = 0, int minorVersion = 0, int userId = 0) + { + var certificateDetails = await this.myLearningService.GetResourceCertificateDetails((userId == 0) ? this.CurrentUserId.GetValueOrDefault() : (int)userId, resourceReferenceId, majorVersion, minorVersion); + return this.Ok(certificateDetails); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ProviderController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ProviderController.cs index 48da8b2b8..43b06c8b0 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ProviderController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ProviderController.cs @@ -1,6 +1,10 @@ namespace LearningHub.NHS.OpenAPI.Controllers { + using System; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Provider; + using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,15 +22,20 @@ public class ProviderController : OpenApiControllerBase /// private readonly IProviderService providerService; + /// + /// The User Provider service. + /// + private readonly IUserProviderService userProviderService; + /// /// Initializes a new instance of the class. /// - /// - /// The Provider service. - /// The logger. - public ProviderController(IProviderService providerService) + /// The Provider service. + /// The userProviderService. + public ProviderController(IProviderService providerService, IUserProviderService userProviderService) { this.providerService = providerService; + this.userProviderService = userProviderService; } /// @@ -75,5 +84,25 @@ public async Task GetProvidersByResourceVersionIdAsync(int resourc return this.Ok(await providerService.GetByResourceVersionIdAsync(resourceVersionId)); } + /// + /// Update user providers. + /// + /// The user provider update model. + /// The . + [HttpPost] + [Route("UpdateUserProvider")] + public async Task UpdateUserProviderAsync(UserProviderUpdateViewModel userProviderUpdateViewModel) + { + try + { + var vr = await this.userProviderService.UpdateUserProviderAsync(userProviderUpdateViewModel); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index e086f9eab..b62847a6d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -7,7 +7,9 @@ namespace LearningHub.NHS.OpenAPI.Controllers using System.Threading.Tasks; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Resource.Admin; using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.Models.Resource.ResourceDisplay; using LearningHub.Nhs.Models.Validation; @@ -291,6 +293,20 @@ public async Task GetFileStatusDetailsAsync([FromQuery] int[] file return this.Ok(await this.resourceService.GetFileStatusDetailsAsync(fileIds)); } + /// + /// The create resource version validation result async. + /// + /// The validationResultViewModel. + /// The . + [HttpPost] + [Route("CreateResourceVersionValidationResult")] + public async Task CreateResourceVersionValidationResultAsync(ResourceVersionValidationResultViewModel validationResultViewModel) + { + var vr = await this.resourceService.CreateResourceVersionValidationResultAsync(validationResultViewModel); + + return this.Ok(new ApiResponse(true, vr)); + } + /// /// Get all file types. /// @@ -1242,7 +1258,6 @@ public async Task UpdateAssessmentResourceVersionAsync(Assessment } } - /// /// The save file chunk detail async. /// @@ -1320,6 +1335,120 @@ public async Task SaveFileDetailsAsync(FileCreateRequestViewModel } } + /// + /// Get a filtered page of User records. + /// + /// The filter. + /// The . + [HttpPost] + [Route("GetResourceAdminSearchFilteredPage")] + public async Task GetResourceAdminSearchFilteredPage([FromBody] PagingRequestModel pagingRequestModel) + { + var pagedResultSet = await this.resourceService.GetResourceAdminSearchFilteredPageAsync(this.CurrentUserId.GetValueOrDefault(), pagingRequestModel); + return this.Ok(pagedResultSet); + } + + /// + /// Transfer Resource Ownership. + /// + /// The transferResourceOwnershipViewModel. + /// The . + [HttpPost] + [Route("TransferResourceOwnership")] + public async Task TransferResourceOwnershipAsync(TransferResourceOwnershipViewModel transferResourceOwnershipViewModel) + { + var vr = await this.resourceService.TransferResourceOwnership(transferResourceOwnershipViewModel, this.CurrentUserId.GetValueOrDefault()); + + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// The get resource version events. + /// + /// The resourceVersionId. + /// The . + [HttpGet] + [Route("GetResourceVersionEvents/{resourceVersionId}")] + public async Task GetResourceVersionEventsAsync(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetResourceVersionEventsAsync(resourceVersionId)); + } + + /// + /// Create resource version event. + /// + /// resourceVersionEventViewModel. + /// The . + [HttpPost] + [Route("CreateResourceVersionEvent")] + public IActionResult CreateResourceVersionEvent(ResourceVersionEventViewModel resourceVersionEventViewModel) + { + if (resourceVersionEventViewModel.CreateUserId == 0) + { + resourceVersionEventViewModel.CreateUserId = this.CurrentUserId.GetValueOrDefault(); + } + + this.resourceService.CreateResourceVersionEvent(resourceVersionEventViewModel); + var vr = new LearningHubValidationResult(true); + + return this.Ok(new ApiResponse(true, vr)); + } + + /// + /// Get specific ResourceVersionViewModel by Id. + /// + /// The resourceVersionId. + /// The . + [HttpGet("GetResourceVersionExtendedViewModel/{resourceVersionId}")] + public async Task GetResourceVersionExtendedViewModelAsync(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetResourceVersionExtendedViewModelAsync(resourceVersionId, this.CurrentUserId.GetValueOrDefault())); + } + + /// + /// The get resource version Dev Id details. + /// + /// The resourceVersionId. + /// The . + [HttpGet] + [Route("GetResourceVersionDevIdDetails/{resourceVersionId}")] + public async Task GetResourceVersionDevIdDetails(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetResourceVersionDevIdDetailsAync(resourceVersionId)); + } + + /// + /// To check devId already exists against a resource. + /// + /// The devId. + /// The . + [HttpGet] + [Route("DoesDevIdExists/{devId}")] + public async Task DoesDevIdExists(string devId) + { + return this.Ok(await this.resourceService.DoesDevIdExistsAync(devId)); + } + + /// + /// Update dev Id details. + /// + /// The ResourceVersionDevIdViewModel. + /// The . + [HttpPut] + [Route("UpdateDevId")] + public async Task UpdateDevId([FromBody] ResourceVersionDevIdViewModel resourceVersionDevIdViewModel) + { + await this.resourceService.UpdateDevIdDetailsAsync(resourceVersionDevIdViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(); + } + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs index bd8a17605..b8a0b5aa2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs @@ -1,9 +1,13 @@ namespace LearningHub.NHS.OpenAPI.Controllers { + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Search.SearchClick; + using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; using LearningHub.Nhs.OpenApi.Services.Interface.Services; @@ -19,6 +23,8 @@ public class SearchController : OpenApiControllerBase { private readonly ISearchService searchService; + private readonly IResourceService resourceService; + private readonly IResourceReferenceService resourceReferenceService; private readonly ICatalogueService catalogueService; private readonly IBookmarkRepository bookmarkRepository; private readonly IProviderService providerService; @@ -29,6 +35,7 @@ public class SearchController : OpenApiControllerBase /// /// The user service. /// The search service. + /// The resource service. /// The logger. /// The catalogue service. /// The provider service. @@ -37,6 +44,8 @@ public class SearchController : OpenApiControllerBase public SearchController( IUserService userService, ISearchService searchService, + IResourceService resourceService, + IResourceReferenceService resourceReferenceService, ILogger logger, ICatalogueService catalogueService, IProviderService providerService, @@ -44,12 +53,186 @@ public SearchController( IBookmarkRepository bookmarkRepository) { this.searchService = searchService; + this.resourceService = resourceService; + this.resourceReferenceService = resourceReferenceService; this.catalogueService = catalogueService; this.providerService = providerService; this.bookmarkRepository = bookmarkRepository; this.findwiseConfig = findwiseConfig.Value; } + /// + /// Get result. + /// + /// The search request model. + /// The . + [HttpPost] + [Route("GetResults")] + public async Task GetResults(SearchRequestModel searchRequestModel) + { + var searchViewModel = new SearchViewModel(); + + searchViewModel = await this.GetSearchResults(searchRequestModel, searchViewModel); + return this.Ok(searchViewModel); + } + + /// + /// Get result. + /// + /// + /// The catalog search request model. + /// + /// + /// The . + /// + [HttpPost] + [Route("GetCatalogueResults")] + public async Task GetCatalogueResults(CatalogueSearchRequestModel catalogueSearchRequestModel) + { + var vm = await this.GetCatalogueSearchResults(catalogueSearchRequestModel); + return this.Ok(vm); + } + + /// + /// The submit feedback. + /// + /// The search feedback. + /// The task. + [HttpPost] + [Route("SubmitFeedback")] + public async Task SubmitFeedback(SearchFeedBackModel searchFeedbackModel) + { + var vr = await this.searchService.SubmitFeedbackAsync(searchFeedbackModel, this.CurrentUserId.GetValueOrDefault()); + + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Create resoruce search action. + /// + /// + /// The search action resource model. + /// + /// + /// Nothing. + /// + [HttpPost] + [Route("CreateResourceSearchAction")] + public async Task CreateResourceSearchAction(SearchActionResourceModel searchActionResourceModel) + { + var vr = await this.searchService.CreateResourceSearchActionAsync(searchActionResourceModel, this.CurrentUserId.GetValueOrDefault()); + var eventCreated = await this.searchService.SendResourceSearchEventClickAsync(searchActionResourceModel); + + if (vr.IsValid && eventCreated) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Create catalogue search action. + /// + /// + /// The search action catalogue model. + /// + /// + /// Nothing. + /// + [HttpPost] + [Route("CreateCatalogueSearchAction")] + public async Task CreateCatalogueSearchAction(SearchActionCatalogueModel searchActionCatalogueModel) + { + var vr = await this.searchService.CreateCatalogueSearchActionAsync(searchActionCatalogueModel, this.CurrentUserId.GetValueOrDefault()); + var eventCreated = await this.searchService.SendCatalogueSearchEventAsync(searchActionCatalogueModel); + + if (vr.IsValid && eventCreated) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Create search term action. + /// + /// The search term action request model. + /// Nothing. + [HttpPost] + [Route("CreateSearchTermAction")] + public async Task CreateSearchTermAction(SearchRequestModel searchRequestModel) + { + LearningHubValidationResult validationResult = await this.searchService.CreateSearchTermEvent(searchRequestModel, this.CurrentUserId.GetValueOrDefault()); + + if (validationResult.IsValid) + { + return this.Ok(new ApiResponse(true, validationResult)); + } + else + { + return this.BadRequest(new ApiResponse(false, validationResult)); + } + } + + /// + /// Create catalogue search term action. + /// + /// The catalogue search term action request model. + /// Nothing. + [HttpPost] + [Route("CreateCatalogSearchTermAction")] + public async Task CreateCatalogueSearchTermAction(CatalogueSearchRequestModel catalogueSearchRequestModel) + { + LearningHubValidationResult validationResult = await this.searchService.CreateCatalogueSearchTermEvent(catalogueSearchRequestModel, this.CurrentUserId.GetValueOrDefault()); + + if (validationResult.IsValid) + { + return this.Ok(new ApiResponse(true, validationResult)); + } + else + { + return this.BadRequest(new ApiResponse(false, validationResult)); + } + } + + /// + /// Send AutoSuggestion Click action. + /// + /// + /// The click Payload model. + /// + /// + /// Nothing. + /// + [HttpPost] + [Route("SendAutoSuggestionClickAction")] + public async Task SendAutoSuggestionClickAction(AutoSuggestionClickPayloadModel clickPayloadModel) + { + var eventCreated = await this.searchService.SendAutoSuggestionEventAsync(clickPayloadModel); + + if (eventCreated) + { + return this.Ok(new ApiResponse(true)); + } + else + { + return this.BadRequest(new ApiResponse(false)); + } + } + /// /// Get AllCatalogue search result. /// @@ -76,6 +259,179 @@ public async Task GetAutoSuggestionResults(string term) return this.Ok(autosuggestionViewModel); } + /// + /// Get search result. + /// + /// The search request model. + /// The search view model. + /// The . + private async Task GetSearchResults(SearchRequestModel searchRequestModel, SearchViewModel searchViewModel) + { + var results = await this.searchService.GetSearchResultAsync(searchRequestModel, this.CurrentUserId.GetValueOrDefault()); + var documents = results.DocumentList.Documents.ToList(); + var catalogueIds = results.DocumentList.Documents.Select(x => x.CatalogueIds.FirstOrDefault()).Where(x => x != 0).ToHashSet().ToList(); + var catalogues = this.catalogueService.GetCataloguesByNodeId(catalogueIds); + var allProviders = await this.providerService.GetAllAsync(); + + foreach (var document in documents) + { + if (document.ProviderIds?.Count > 0) + { + document.Providers = allProviders.Where(n => document.ProviderIds.Contains(n.Id)).ToList(); + } + + if (document.CatalogueIds.Any(x => x == 1)) + { + continue; + } + + var catalogue = catalogues.SingleOrDefault(x => x.NodeId == document.CatalogueIds.SingleOrDefault()); + + if (catalogue == null) + { + continue; + } + + document.CatalogueUrl = catalogue.Url; + document.CatalogueBadgeUrl = catalogue.BadgeUrl; + document.CatalogueName = catalogue.Name; + + if (catalogue.RestrictedAccess) + { + var roleUserGroups = await this.catalogueService.GetRoleUserGroupsForCatalogueSearch(catalogue.NodeId, this.CurrentUserId.GetValueOrDefault()); + document.CatalogueRestrictedAccess = catalogue.RestrictedAccess; + document.CatalogueHasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) + && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); + } + } + + searchViewModel.DocumentModel = results.DocumentList.Documents.ToList(); + searchViewModel.SearchString = searchRequestModel.SearchText; + searchViewModel.Hits = results.DocumentList.Documents.Count(); + searchViewModel.DescriptionMaximumLength = this.findwiseConfig.MaximumDescriptionLength; + searchViewModel.ErrorOnAPI = results.ErrorsOnAPICall; + searchViewModel.Facets = results.Facets; + + if (results.Stats != null) + { + searchViewModel.TotalHits = results.Stats.TotalHits; + } + + searchViewModel.SearchId = searchRequestModel.SearchId > 0 ? searchRequestModel.SearchId : results.SearchId; + + // Add additional resource attributes + foreach (var document in searchViewModel.DocumentModel) + { + if (int.TryParse(document.Id, out int resourceId)) + { + var resource = await this.resourceService.GetResourceByIdAsync(resourceId); + + if (resource != null && resource.CurrentResourceVersionId.HasValue) + { + document.ResourceVersionId = resource.CurrentResourceVersionId.Value; + + var resourceRef = await this.resourceReferenceService.GetByIdAsync(document.ResourceReferenceId); + if (resourceRef != null) + { + document.NodePathId = resourceRef.NodePathId; + } + } + } + } + + // Add related Catalogue information to the search results. + var relatedCatalogueIds = new List(); + foreach (var document in results.DocumentList.Documents) + { + foreach (int catalogueId in document.CatalogueIds) + { + if (relatedCatalogueIds.IndexOf(catalogueId) == -1) + { + relatedCatalogueIds.Add(catalogueId); + } + } + } + + searchViewModel.Feedback = results.Feedback; + searchViewModel.RelatedCatalogues = await this.catalogueService.GetCatalogues(catalogueIds); + searchViewModel.Spell = results.Spell; + + return searchViewModel; + } + + /// + /// Get catalogue search result. + /// + /// The catalog search request model. + /// The . + private async Task GetCatalogueSearchResults(CatalogueSearchRequestModel catalogueSearchRequestModel) + { + var results = await this.searchService.GetCatalogueSearchResultAsync(catalogueSearchRequestModel, this.CurrentUserId.GetValueOrDefault()); + + var documents = results.DocumentList.Documents.ToList(); + var documentIds = documents.Select(x => int.Parse(x.Id)).ToList(); + var catalogues = this.catalogueService.GetCataloguesByNodeId(documentIds); + var bookmarks = this.bookmarkRepository.GetAll().Where(b => documentIds.Contains(b.NodeId ?? -1) && b.UserId == this.CurrentUserId); + var allProviders = await this.providerService.GetAllAsync(); + + foreach (var document in documents) + { + var catalogue = catalogues.SingleOrDefault(x => x.NodeId == int.Parse(document.Id)); + if (catalogue == null) + { + continue; + } + + // catalogue.No + document.Url = catalogue.Url; + document.BannerUrl = catalogue.BannerUrl; + document.BadgeUrl = catalogue.BadgeUrl; + document.CardImageUrl = catalogue.CardImageUrl; + document.NodePathId = catalogue.NodePathId; + if (catalogue.RestrictedAccess) + { + var roleUserGroups = await this.catalogueService.GetRoleUserGroupsForCatalogueSearch(catalogue.NodeId, this.CurrentUserId.GetValueOrDefault()); + document.RestrictedAccess = catalogue.RestrictedAccess; + document.HasAccess = roleUserGroups.Any(x => x.UserGroup.UserUserGroup.Any(y => y.UserId == this.CurrentUserId) + && (x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Reader)); + } + + var bookmark = bookmarks.FirstOrDefault(x => x.NodeId == int.Parse(document.Id)); + if (bookmark != null) + { + document.BookmarkId = bookmark?.Id; + document.IsBookmarked = !bookmark?.Deleted ?? false; + } + + if (document.ProviderIds?.Count > 0) + { + document.Providers = allProviders.Where(n => document.ProviderIds.Contains(n.Id)).ToList(); + } + } + + var searchViewModel = new SearchCatalogueViewModel + { + DocumentModel = documents, + SearchString = catalogueSearchRequestModel.SearchText, + Hits = results.DocumentList.Documents.Count(), + DescriptionMaximumLength = this.findwiseConfig.MaximumDescriptionLength, + ErrorOnAPI = results.ErrorsOnAPICall, + Facets = results.Facets, + }; + + if (results.Stats != null) + { + searchViewModel.TotalHits = results.Stats.TotalHits; + } + + searchViewModel.SearchId = catalogueSearchRequestModel.SearchId > 0 ? catalogueSearchRequestModel.SearchId : results.SearchId; + searchViewModel.Feedback = results.Feedback; + searchViewModel.Spell = results.Spell; + + return searchViewModel; + } + + /// /// Get AutoSuggestion Results. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index 1fb149c79..e83fe498a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using System.Threading.Tasks; + using System.Web; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Entities; @@ -14,8 +15,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; - using System.Security.Claims; - using System.Security.Principal; /// /// The log controller. @@ -113,6 +112,26 @@ public async Task> UpdateUserProfileAsync(UserProfile } } + /// + /// Get a filtered page of LH User records. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The preset filter. + /// The filter. + /// The . + [HttpGet] + [Route("GetLHUserAdminBasicFilteredPage/{page}/{pageSize}/{sortColumn}/{sortDirection}/{presetFilter}/{filter}")] + public async Task GetLHUserAdminBasicFilteredPage(int page, int pageSize, string sortColumn, string sortDirection, string presetFilter, string filter) + { + presetFilter = HttpUtility.UrlDecode(presetFilter); + filter = HttpUtility.UrlDecode(filter); + PagedResultSet pagedResultSet = await this.userService.GetUserAdminBasicPageAsync(page, pageSize, sortColumn, sortDirection, presetFilter, filter); + return this.Ok(pagedResultSet); + } + /// /// Get specific User Profile by Id. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserGroupController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserGroupController.cs new file mode 100644 index 000000000..8a362faf1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserGroupController.cs @@ -0,0 +1,380 @@ +namespace LearningHub.Nhs.Api.Controllers +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using LearningHub.NHS.OpenAPI.Controllers; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + + /// + /// User Group operations. + /// + [Authorize] + [Route("UserGroup")] + [ApiController] + public class UserGroupController : OpenApiControllerBase + { + /// + /// The user group service. + /// + private readonly IUserGroupService userGroupService; + + /// + /// Initializes a new instance of the class. + /// + /// The user group service. + + public UserGroupController(IUserGroupService userGroupService) + { + this.userGroupService = userGroupService; + } + + /// + /// Returns the UserGroupAdminDetail model for a particular user group id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetUserGroupAdminDetailById/{id}")] + public async Task GetUserGroupAdminDetailById(int id) + { + var userGroup = await this.userGroupService.GetUserGroupAdminDetailByIdAsync(id); + + return this.Ok(userGroup); + } + + /// + /// Returns the UserGroupAdminDetail model for a particular user group id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetUserGroupAdminRoleDetailById/{id}")] + public async Task GetUserGroupAdminRoleDetailById(int id) + { + var retVal = await this.userGroupService.GetUserGroupRoleDetailByUserGroupId(id); + + return this.Ok(retVal); + } + + /// + /// Returns the user group - role detail for a particular user id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetUserGroupRoleDetailByUserId/{userId}")] + public async Task GetRoleUserGroupDetailByUserId(int userId) + { + var retVal = await this.userGroupService.GetRoleUserGroupDetailByUserId(userId); + + return this.Ok(retVal); + } + + /// + /// Returns the user group - role detail for a current user id. + /// + /// The . + [HttpGet] + [Route("GetUserGroupRoleDetail")] + public async Task GetRoleUserGroupDetail() + { + var retVal = await this.userGroupService.GetRoleUserGroupDetailByUserId(this.CurrentUserId.GetValueOrDefault()); + + return this.Ok(retVal); + } + + /// + /// Create a User Group. + /// + /// The user group. + /// The . + [HttpPost] + [Route("CreateUserGroup")] + public async Task CreateUserGroupAsync([FromBody]UserGroupAdminDetailViewModel userGroup) + { + var vr = await this.userGroupService.CreateUserGroupAsync(userGroup, this.CurrentUserId.GetValueOrDefault()); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Update an existing User Group. + /// + /// The user. + /// The . + [HttpPost] + [Route("UpdateUserGroup")] + public async Task UpdateUserGroup(UserGroupAdminDetailViewModel userGroup) + { + var vr = await this.userGroupService.UpdateUserGroupAsync(userGroup, this.CurrentUserId.GetValueOrDefault()); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Delete an existing User Group. + /// + /// The user. + /// The . + [HttpPost] + [Route("DeleteUserGroup")] + public async Task DeleteUserGroup(UserGroupAdminBasicViewModel userGroup) + { + var vr = await this.userGroupService.DeleteAsync(this.CurrentUserId.GetValueOrDefault(), userGroup.Id); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Add Users to User Group. + /// + /// The user. + /// The . + [HttpPost] + [Route("AddUserUserGroups")] + public async Task AddUserUserGroups(List userUserGroups) + { + try + { + var vr = await this.userGroupService.AddUserUserGroups(userUserGroups, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Add Role - User Group - Scope association. + /// + /// The user. + /// The . + [HttpPost] + [Route("AddRoleUserGroups")] + public async Task AddRoleUserGroups(List roleUserGroups) + { + try + { + var vr = await this.userGroupService.AddRoleUserGroups(roleUserGroups, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Removes User from a User Group. + /// + /// The user. + /// The . + [HttpPost] + [Route("DeleteUserUserGroup")] + public async Task DeleteUserUserGroupAsync(UserUserGroupViewModel userUserGroupViewModel) + { + try + { + var vr = await this.userGroupService.DeleteUserUserGroupAsync(userUserGroupViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Add User Group - Attribute. + /// + /// The user. + /// The . + [HttpPost] + [Route("AddUserGroupAttribute")] + public async Task AddUserGroupAttributeAsync(UserGroupAttributeViewModel userGroupAttribute) + { + try + { + var vr = await this.userGroupService.AddUserGroupAttribute(userGroupAttribute, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Removes a User Group Attribute. + /// + /// The user. + /// The . + [HttpPost] + [Route("DeleteUserGroupAttribute")] + public async Task DeleteUserGroupAttributeAsync(UserGroupAttributeViewModel userGroupAttribute) + { + try + { + var vr = await this.userGroupService.DeleteUserGroupAttributeAsync(userGroupAttribute, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Removes a Role - User Group. + /// + /// The roleUserGroupUpdateViewModel. + /// The . + [HttpPost] + [Route("DeleteRoleUserGroup")] + public async Task DeleteRoleUserGroupAsync(RoleUserGroupUpdateViewModel roleUserGroupUpdateViewModel) + { + try + { + var vr = await this.userGroupService.DeleteRoleUserGroupAsync(roleUserGroupUpdateViewModel, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + + /// + /// Get a filtered page of User records. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The preset filter. + /// The filter. + /// The . + [HttpGet] + [Route("GetUserGroupAdminBasicFilteredPage/{page}/{pageSize}/{sortColumn}/{sortDirection}/{presetFilter}/{filter}")] + public async Task GetUserGroupAdminBasicFilteredPage(int page, int pageSize, string sortColumn, string sortDirection, string presetFilter, string filter) + { + PagedResultSet pagedResultSet = await this.userGroupService.GetUserGroupAdminBasicPageAsync(page, pageSize, sortColumn, sortDirection, presetFilter, filter); + return this.Ok(pagedResultSet); + } + + /// + /// Get a filtered page of User records. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + [HttpGet] + [Route("GetUserUserGroupAdminFilteredPage/{page}/{pageSize}/{sortColumn}/{sortDirection}/{presetFilter}/{filter}")] + public async Task GetUserUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn, string sortDirection, string presetFilter, string filter) + { + PagedResultSet pagedResultSet = await this.userGroupService.GetUserUserGroupAdminFilteredPage(page, pageSize, sortColumn, sortDirection, presetFilter, filter); + return this.Ok(pagedResultSet); + } + + /// + /// Get a filtered page of role user group records. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The presetFilter. + /// The filter. + /// The . + [HttpGet] + [Route("GetRoleUserGroupAdminFilteredPage/{page}/{pageSize}/{sortColumn}/{sortDirection}/{presetFilter}/{filter}")] + public async Task GetRoleUserGroupAdminFilteredPage(int page, int pageSize, string sortColumn, string sortDirection, string presetFilter, string filter) + { + PagedResultSet pagedResultSet = await this.userGroupService.GetRoleUserGroupAdminFilteredPage(page, pageSize, sortColumn, sortDirection, presetFilter, filter); + return this.Ok(pagedResultSet); + } + + /// + /// Get specific UserGroup by Id. + /// + /// The id. + /// The include roles. + /// The . + [HttpGet("{id}/{includeRoles}")] + public async Task> GetAsync(int id, bool includeRoles) + { + return this.Ok(await this.userGroupService.GetByIdAsync(id, includeRoles)); + } + + /// + /// Create a new UserGroup. + /// + /// The user group. + /// The . + //// todo[Authorize(Roles = "System Administrator")] + [HttpPost] + public async Task CreateAsync([FromBody] UserGroup userGroup) + { + var vr = await this.userGroupService.CreateAsync(this.CurrentUserId.GetValueOrDefault(), userGroup); + + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Delete specific UserGroup by Id. + /// + /// The id. + /// The . + [HttpDelete("{id}")] + public async Task DeleteAsync(int id) + { + var vr = await this.userGroupService.DeleteAsync(this.CurrentUserId.GetValueOrDefault(), id); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserLearningRecordController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserLearningRecordController.cs new file mode 100644 index 000000000..bfc455ecb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserLearningRecordController.cs @@ -0,0 +1,67 @@ +namespace LearningHub.Nhs.Api.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.NHS.OpenAPI.Controllers; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + /// + /// UserLearningRecord operations. + /// + [Route("UserLearningRecord")] + [ApiController] + [Authorize] + public class UserLearningRecordController : OpenApiControllerBase + { + /// + /// The MyLearning service. + /// + private readonly IUserLearningRecordService userLearningRecordService; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The myLearning service. + /// + public UserLearningRecordController(IUserLearningRecordService userLearningRecordService) + { + this.userLearningRecordService = userLearningRecordService; + } + + /// + /// Get UserHistory record by id. + /// + /// + /// The page. + /// + /// + /// The page size. + /// + /// + /// The sort column. + /// + /// + /// The sort direction. + /// + /// + /// The preset filter. + /// + /// + /// The filter. + /// + /// + /// The . + /// + [HttpGet] + [Route("GetUserLearningRecords/{page}/{pageSize}/{sortColumn}/{sortDirection}/{presetFilter}/{filter}")] + public async Task GetUserLearningRecordsAsync(int page, int pageSize, string sortColumn, string sortDirection, string presetFilter, string filter) + { + PagedResultSet pagedResultSet = await this.userLearningRecordService.GetUserLearningRecordsAsync(page, pageSize, sortColumn, sortDirection, presetFilter, filter); + return this.Ok(pagedResultSet); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserNotificationController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserNotificationController.cs new file mode 100644 index 000000000..8bb1dcdc1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserNotificationController.cs @@ -0,0 +1,143 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Notification; + using LearningHub.Nhs.Models.Paging; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + /// + /// UserNotification operations. + /// + [Authorize(Policy = "AuthorizeOrCallFromLH")] + [Route("UserNotification")] + [ApiController] + public class UserNotificationController : OpenApiControllerBase + { + /// + /// The usernotification service. + /// + private readonly IUserNotificationService usernotificationService; + + /// + /// Initializes a new instance of the class. + /// + /// The user service. + /// The usernotification service. + /// The logger. + public UserNotificationController(IUserNotificationService usernotificationService) + { + this.usernotificationService = usernotificationService; + } + + // GET api/UserNotification/GetById/id + + /// + /// Get User Notification record by id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetById/{id}")] + public async Task GetById(int id) + { + var usernotification = await usernotificationService.GetByIdAsync(id); + + return this.Ok(usernotification); + } + + /// + /// Get User Notification record by id and user Id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetByIdAndUserId/{id}")] + public async Task GetByIdAndUserId(int id) + { + var usernotification = await usernotificationService.GetByIdAndUserIdAsync(id, this.CurrentUserId.GetValueOrDefault()); + + return this.Ok(usernotification); + } + + // GET api/UserNotification/GetPage/page/pageSize + + /// + /// Get a page of user notifications. + /// + /// Paging request. + /// Notification priority type. + /// The . + [HttpPost] + [Route("GetPage/{priorityType}")] + public async Task GetPage([FromBody] PagingRequestModel request, NotificationPriorityEnum priorityType) + { + PagedResultSet pagedResultSet = await usernotificationService.GetPageAsync( + this.CurrentUserId.GetValueOrDefault(), priorityType, request.Page, request.PageSize, request.SortColumn, request.SortDirection); + return this.Ok(pagedResultSet); + } + + // GET api/UserNotification/GetUserUnreadCount/userid + + /// + /// Get User Unread Notifications Count. + /// + /// The userid. + /// The . + [HttpGet] + [Route("GetUserUnreadNotificationCount/{userid}")] + public async Task GetUserUnreadNotificationCount(int userid) + { + var count = await usernotificationService.GetUserUnreadNotificationCountAsync(this.CurrentUserId.GetValueOrDefault()); + + return this.Ok(count); + } + + /// + /// Update an existing user notification. + /// + /// The notification. + /// The . + [HttpPut("{usernotification}")] + public async Task PutAsync([FromBody] UserNotification notification) + { + var vr = await usernotificationService.UpdateAsync(this.CurrentUserId.GetValueOrDefault(), notification); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Create a new user notification. + /// + /// The notification. + /// The . + [HttpPost("{usernotification}")] + public async Task PostAsync([FromBody] UserNotification notification) + { + if (notification.UserId == 0) + { + notification.UserId = this.CurrentUserId.GetValueOrDefault(); + } + + var vr = await usernotificationService.CreateAsync(this.CurrentUserId.GetValueOrDefault(), notification); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + } +} \ No newline at end of file From a4b7ce1c6246dc4b8a5b2a196bac149beffd7b27 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 4 Jun 2025 10:10:26 +0100 Subject: [PATCH 02/83] api route fix --- .../LearningHub.Nhs.OpenApi/Controllers/ContentController.cs | 2 +- .../LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs index f7c03834e..4601b8f79 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ContentController.cs @@ -13,7 +13,7 @@ /// The ContentController. /// [Authorize(Policy = "AuthorizeOrCallFromLH")] - [Route("api/content")] + [Route("Content")] [ApiController] public class ContentController : OpenApiControllerBase { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs index 29e21f5e7..4bade7a6a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs @@ -10,7 +10,7 @@ /// The DashboardController. /// [Authorize(Policy = "AuthorizeOrCallFromLH")] - [Route("api/dashboard")] + [Route("Dashboard")] [ApiController] public class DashboardController : OpenApiControllerBase { From fbdd80cae6cbd3766fb55e2c9460dfc5d5a0b92f Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 4 Jun 2025 10:24:38 +0100 Subject: [PATCH 03/83] . --- .../Repositories/Content/PageSectionDetailRepository.cs | 1 - OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs index 04ecb1258..baf81f7d0 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Content/PageSectionDetailRepository.cs @@ -1,6 +1,5 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Content { - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LearningHub.Nhs.Models.Entities.Content; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs index c181b1092..c8099d1ed 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs @@ -132,7 +132,7 @@ private static void AddRepositoryImplementations(this IServiceCollection service // Content services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); } } From 2f30da1b67fdfae44b297063b92fa31b7b40c585 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Fri, 6 Jun 2025 14:40:10 +0100 Subject: [PATCH 04/83] P3 endpoint fix --- .../EntityFramework/ServiceMappings.cs | 3 ++ .../Map/Hierarchy/HierarchyEditDetailMap.cs | 33 +++++++++++++++ .../Map/Hierarchy/HierarchyEditMap.cs | 41 +++++++++++++++++++ .../Map/Hierarchy/NodeResourceLookupMap.cs | 33 +++++++++++++++ .../Services/CatalogueService.cs | 4 +- 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 2464e5953..87556117f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -148,12 +148,15 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs new file mode 100644 index 000000000..893fbfc25 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The hierarchy edit map. + /// + public class HierarchyEditDetailMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("HierarchyEditDetail", "hierarchy"); + + modelBuilder.HasOne(d => d.HierarchyEdit) + .WithMany(p => p.HierarchyEditDetail) + .HasForeignKey(d => d.HierarchyEditId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEditDetail_HierarchyEdit"); + + modelBuilder.Property(e => e.HierarchyEditDetailType).HasColumnName("HierarchyEditDetailTypeId") + .HasConversion(); + + modelBuilder.Property(e => e.HierarchyEditDetailOperation).HasColumnName("HierarchyEditDetailOperationId") + .HasConversion(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs new file mode 100644 index 000000000..23883290e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The hierarchy edit map. + /// + public class HierarchyEditMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("HierarchyEdit", "hierarchy"); + + modelBuilder.Property(e => e.HierarchyEditStatus).HasColumnName("HierarchyEditStatusId") + .HasConversion(); + + modelBuilder.HasOne(d => d.CreateUser) + .WithMany(p => p.HierarchyEdit) + .HasForeignKey(d => d.CreateUserId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEdit_CreateUser"); + + modelBuilder.HasOne(d => d.RootNode) + .WithMany() + .HasForeignKey(d => d.RootNodeId) + .HasConstraintName("FK_HierarchyEdit_Node"); + + modelBuilder.HasOne(d => d.Publication) + .WithMany() + .HasForeignKey(d => d.PublicationId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEdit_Publication"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs new file mode 100644 index 000000000..d0d98093f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The node resource lookup map. + /// + public class NodeResourceLookupMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("NodeResourceLookup", "hierarchy"); + + modelBuilder.HasOne(d => d.Node) + .WithMany() + .HasForeignKey(d => d.NodeId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_NodeResourceLookup_Node"); + + modelBuilder.HasOne(d => d.Resource) + .WithMany() + .HasForeignKey(d => d.ResourceId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_NodeResourceLookup_Resource"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index ab0592181..c83474c1f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -76,7 +76,7 @@ public class CatalogueService : ICatalogueService /// /// /// - public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepository nodeRepository, IUserUserGroupRepository userUserGroupRepository, IMapper mapper, IOptions findwiseConfig, IOptions learningHubConfig, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, INodeResourceRepository nodeResourceRepository, IResourceVersionRepository resourceVersionRepository, IRoleUserGroupRepository roleUserGroupRepository, IProviderService providerService, ICatalogueAccessRequestRepository catalogueAccessRequestRepository, IUserRepository userRepository, IUserProfileRepository userProfileRepository, IEmailSenderService emailSenderService, IBookmarkRepository bookmarkRepository,INodeActivityRepository nodeActivityRepository, IFindwiseApiFacade findwiseApiFacade) + public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepository nodeRepository, IUserUserGroupRepository userUserGroupRepository, IMapper mapper, IOptions findwiseConfig, IOptions learningHubConfig, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, INodeResourceRepository nodeResourceRepository, IResourceVersionRepository resourceVersionRepository, IRoleUserGroupRepository roleUserGroupRepository, IProviderService providerService, ICatalogueAccessRequestRepository catalogueAccessRequestRepository, IUserRepository userRepository, IUserProfileRepository userProfileRepository, IEmailSenderService emailSenderService, IBookmarkRepository bookmarkRepository,INodeActivityRepository nodeActivityRepository, IFindwiseApiFacade findwiseApiFacade, INotificationSenderService notificationSenderService, ITimezoneOffsetManager timezoneOffsetManager) { this.catalogueRepository = catalogueRepository; this.nodeRepository = nodeRepository; @@ -96,6 +96,8 @@ public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepositor this.findwiseApiFacade = findwiseApiFacade; this.learningHubConfig = learningHubConfig.Value; this.findwiseConfig = findwiseConfig.Value; + this.timezoneOffsetManager = timezoneOffsetManager; + this.notificationSenderService = notificationSenderService; } /// From 03d41a635f9e7263c8ac2e61d8fd729e94a39e2f Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 6 Jun 2025 23:39:08 +0100 Subject: [PATCH 05/83] Moved all nuget packages to central package management --- .../LearningHub.Nhs.AdminUI.csproj | 1262 ++++++++--------- Directory.Build.props | 2 +- Directory.Packages.props | 93 ++ ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 32 +- .../LearningHub.Nhs.WebUI.csproj | 496 ++++--- .../Services/AzureMediaService.cs | 2 +- .../LearningHub.Nhs.OpenApi.Models.csproj | 39 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 46 +- ...earningHub.Nhs.OpenApi.Repositories.csproj | 60 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 46 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 76 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 59 +- .../LearningHub.NHS.OpenAPI.csproj | 79 +- ...ub.Nhs.ReportApi.Services.Interface.csproj | 17 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 28 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 26 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 12 +- .../LearningHub.Nhs.ReportApi.csproj | 39 +- .../LearningHub.Nhs.Api.csproj | 60 +- .../LearningHub.Nhs.Api.Shared.csproj | 23 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 36 +- ...earningHub.Nhs.Repository.Interface.csproj | 34 +- .../LearningHub.Nhs.Repository.csproj | 28 +- .../LearningHub.Nhs.Services.Interface.csproj | 29 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 45 +- .../LearningHub.Nhs.Services.csproj | 44 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 33 +- ...LearningHub.Nhs.Migration.Interface.csproj | 24 +- .../LearningHub.Nhs.Migration.Models.csproj | 27 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 25 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 34 +- .../LearningHub.Nhs.Migration.csproj | 52 +- nuget.config | 18 + 33 files changed, 1431 insertions(+), 1495 deletions(-) create mode 100644 Directory.Packages.props create mode 100644 nuget.config diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index eba6b6ffa..ce9830312 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -1,645 +1,631 @@ - - + net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 31abd8b9-4223-4ff3-896b-a46530c9e15c /subscriptions/57c55d5f-78c1-4373-a021-ff8357548f51/resourceGroups/LearningHubNhsUk-AdminUI-Prod-RG/providers/microsoft.insights/components/LearningHubNhsUk-AdminUI-Prod true true - x64 + x64 - - - - - - - - - - - - - - - - - - - - - - - <_ContentIncludedByDefault Remove="bundleconfig.json" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="bundleconfig.json" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 709a8ce13..c3a7f4e28 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ $(SolutionDir)StyleCop.ruleset - + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..8f1d8febf --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,93 @@ + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index d6bf3cd0d..e12ccd3f4 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -1,37 +1,31 @@ - - + net8.0 enable enable - false - True - - - - - - - - - - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index dc2cf661d..24d3c72f8 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -1,256 +1,242 @@  - - - net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 - InProcess - a2ecb5d2-cf13-4551-9cb6-3d86dfbcf8ef - true - true - x64 - true - - - - Project - http://localhost:5001 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - Always - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - - - + + net8.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 + InProcess + a2ecb5d2-cf13-4551-9cb6-3d86dfbcf8ef + true + true + x64 + true + + + Project + http://localhost:5001 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Services/AzureMediaService.cs b/LearningHub.Nhs.WebUI/Services/AzureMediaService.cs index 6068bc067..f2877b9eb 100644 --- a/LearningHub.Nhs.WebUI/Services/AzureMediaService.cs +++ b/LearningHub.Nhs.WebUI/Services/AzureMediaService.cs @@ -71,7 +71,7 @@ public async Task CreateMediaInputAsset(IFormFile file) string filename = Regex.Replace(file.FileName, "[^a-zA-Z0-9.]", string.Empty); var destContainer = new BlobContainerClient(new Uri(uploadSasUrl)); - var destBlob = destContainer.GetBlockBlobClient(filename.IsNullOrEmpty() ? "file.txt" : filename); + var destBlob = destContainer.GetBlockBlobClient(string.IsNullOrEmpty(filename) ? "file.txt" : filename); await destBlob.UploadAsync(file.OpenReadStream()); return asset.Name; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index f34790d51..baf3dee90 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -1,23 +1,18 @@ - - - net8.0 - true - enable - x64 - - - - - - - - - - - - - - - - + + net8.0 + true + enable + x64 + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 3444f18a2..1617cfa0d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -1,27 +1,21 @@ - - - net8.0 - LearningHub.Nhs.OpenApi.Repositories.Interface - enable - true - x64 - - - - - - - - - - - - - - - - - - - + + net8.0 + LearningHub.Nhs.OpenApi.Repositories.Interface + enable + true + x64 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index b26c815cd..effe5591f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -1,33 +1,27 @@ - - - - net8.0 - LearningHub.Nhs.OpenApi.Repositories - enable - true - x64 - - - - - - - - - - - - - - - - - - - - - - - - - + + + net8.0 + LearningHub.Nhs.OpenApi.Repositories + enable + true + x64 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 20b3a1c06..274460624 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -1,27 +1,21 @@ - - - net8.0 - enable - true - x64 - - - - - - - - - - - - - - - - - - - - + + net8.0 + enable + true + x64 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index f84d5121c..465bda0cd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -1,41 +1,35 @@ - - - - net8.0 - LearningHub.Nhs.OpenApi.Services - enable - true - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + net8.0 + LearningHub.Nhs.OpenApi.Services + enable + true + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 87712e074..8974e0fdb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -1,31 +1,28 @@ - - - - net8.0 - false - enable - x64 - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - + + + net8.0 + false + enable + x64 + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 83639e186..0871850e4 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -1,44 +1,37 @@ - - - enable - net8.0 - true - x64 - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - + + enable + net8.0 + true + x64 + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj index 0a697a51f..b8c9379fa 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj @@ -1,30 +1,23 @@ - - + net8.0 enable enable - - - - - - + + + - - - - + \ No newline at end of file diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj index 50e3d7536..114d8ae76 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj @@ -1,46 +1,38 @@ - - + net8.0 enable enable - false - - - - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + \ No newline at end of file diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj index 55c8a1be4..fe3bf575b 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -1,37 +1,29 @@ - - + net8.0 enable enable - - - - - - - - - - - + + + + + + + - - - - + \ No newline at end of file diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj index 2f5518784..b32e219f3 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -1,5 +1,4 @@ - - + net8.0 enable @@ -7,21 +6,16 @@ $(NoWarn),1573,1591,1712 True - - - - + - - - + \ No newline at end of file diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index 583caa9f2..17b31c21d 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -1,42 +1,31 @@ - - + net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 enable enable False - - - - - - - - - - - - - - - - + + + + + + + - PreserveNewest @@ -44,14 +33,12 @@ PreserveNewest - PreserveNewest - - - + + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index bb0c6c6cc..834f8d32c 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -3,49 +3,43 @@ net8.0 1.0.0.0 1.0.0.0 - 1.0.0 + 1.0.0 InProcess 234b55ad-984b-47ae-9d7a-71e372e6ded7 true - x64 + x64 - LearningHub.Nhs.Api.xml - - Always - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + @@ -55,10 +49,12 @@ - - - - + + + + + + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 74d2ccc77..1cfafdb8e 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -1,19 +1,16 @@ - - + net8.0 true - x64 + x64 - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index 9663e4ee7..b024beaf5 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -1,34 +1,30 @@ - - + net8.0 true false - x64 + x64 - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index d41ba9e68..91c49b25f 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -1,27 +1,23 @@ - - + net8.0 true - x64 + x64 - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - + - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 904bda4c8..836a83129 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -1,25 +1,21 @@ - - + net8.0 true - x64 + x64 - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index b259b3a7e..636f10a2e 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -1,31 +1,26 @@ - - + net8.0 true - x64 + x64 - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index f78df0084..08a04e008 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -1,35 +1,32 @@ - - + net8.0 true false - x64 + x64 - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles; analyzers - @@ -37,11 +34,9 @@ - PreserveNewest - - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index e99e5300a..38c383d6c 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -1,31 +1,28 @@ - - + net8.0 true - x64 + x64 - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - @@ -34,5 +31,4 @@ - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 697d1db2f..0a2e1e509 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -1,17 +1,14 @@ - - + Exe net8.0 7.3 true - x64 + x64 - - PreserveNewest @@ -19,24 +16,21 @@ PreserveNewest - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - Always @@ -63,5 +57,4 @@ Always - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 89ca1344e..75f4e5e58 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -1,23 +1,19 @@ - - + net8.0 true - x64 + x64 - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 4ab608bac..9801da32e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -1,21 +1,18 @@ - - + net8.0 true - x64 + x64 - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 7dd7febb7..e21f4c7a4 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -1,28 +1,23 @@ - - + net8.0 true - x64 + x64 - - - - - - + + + + + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index 5b83ec893..48f23a40b 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -1,35 +1,31 @@ - - + net8.0 true false - x64 + x64 - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + - - + \ No newline at end of file diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index bf0235325..9082047ec 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -1,30 +1,26 @@ - - - - net8.0 - true - x64 - - + + + net8.0 + true + x64 + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..4b5a05544 --- /dev/null +++ b/nuget.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file From 340ae1e26791d9a042f62de3c593ed946af8c06c Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Mon, 9 Jun 2025 08:54:09 +0100 Subject: [PATCH 06/83] Added event endpoint to the open api --- .../Controllers/EventController.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/EventController.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/EventController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/EventController.cs new file mode 100644 index 000000000..9770a0ba5 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/EventController.cs @@ -0,0 +1,85 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities.Analytics; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + /// + /// Event operations. + /// + [Authorize(Policy = "AuthorizeOrCallFromLH")] + [Route("Event")] + [ApiController] + public class EventController : OpenApiControllerBase + { + /// + /// The event service. + /// + private readonly IEventService eventService; + + /// + /// Initializes a new instance of the class. + /// + /// The event service. + public EventController( + IEventService eventService) + { + this.eventService = eventService; + } + + /// + /// Get specific event by Id. + /// + /// The id. + /// The . + [HttpGet("{id}")] + public async Task GetAsync(int id) + { + return this.Ok(await eventService.GetByIdAsync(id)); + } + + /// + /// The create event async. + /// + /// The event. + /// The . + [HttpPost] + [Route("Create")] + public async Task CreateAsync([FromBody] Event eventEntity) + { + eventEntity.UserId = !eventEntity.UserId.HasValue ? this.CurrentUserId : eventEntity.UserId; + var vr = await eventService.CreateAsync(this.CurrentUserId.GetValueOrDefault(), eventEntity); + + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + + /// + /// Update an existing Event. + /// + /// The event. + /// The . + [HttpPut] + public async Task PutAsync([FromBody] Event eventEntity) + { + var vr = await eventService.UpdateAsync(this.CurrentUserId.GetValueOrDefault(), eventEntity); + if (vr.IsValid) + { + return this.Ok(new ApiResponse(true, vr)); + } + else + { + return this.BadRequest(new ApiResponse(false, vr)); + } + } + } +} From 021c08065389878f7d9afb199ba6066acb414561 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Fri, 6 Jun 2025 15:18:13 +0100 Subject: [PATCH 07/83] catalogue service test fix --- .../Services/Services/CatalogueServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs index 4d50e8b82..cfda30045 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs @@ -69,7 +69,7 @@ public CatalogueServiceTests() this.findwiseConfig = new Mock>(); this.notificationSenderService = new Mock(); this.timezoneOffsetManager = new Mock(); - this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.nodeRepository.Object, this.userUserGroupRepository.Object, this.mapper.Object, this.findwiseConfig.Object, this.learningHubConfig.Object, this.catalogueNodeVersionRepository.Object, this.nodeResourceRepository.Object, this.resourceVersionRepository.Object, this.roleUserGroupRepository.Object, this.providerService.Object, this.catalogueAccessRequestRepository.Object, this.userRepository.Object, this.userProfileRepository.Object, this.emailSenderService.Object, this.bookmarkRepository.Object, this.nodeActivityRepository.Object, this.findwiseApiFacade.Object); + this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.nodeRepository.Object, this.userUserGroupRepository.Object, this.mapper.Object, this.findwiseConfig.Object, this.learningHubConfig.Object, this.catalogueNodeVersionRepository.Object, this.nodeResourceRepository.Object, this.resourceVersionRepository.Object, this.roleUserGroupRepository.Object, this.providerService.Object, this.catalogueAccessRequestRepository.Object, this.userRepository.Object, this.userProfileRepository.Object, this.emailSenderService.Object, this.bookmarkRepository.Object, this.nodeActivityRepository.Object, this.findwiseApiFacade.Object,this.notificationSenderService.Object,this.timezoneOffsetManager.Object); } private static IEnumerable CatalogueNodeVersionList => new List() From beda631c890b5a4573c7995c1ff1796c3cfe558a Mon Sep 17 00:00:00 2001 From: binon Date: Mon, 9 Jun 2025 09:08:35 +0100 Subject: [PATCH 08/83] Update continuous-integration-workflow.yml --- .github/workflows/continuous-integration-workflow.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index f1609bb44..24c2d6891 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -76,7 +76,9 @@ jobs: dotnet-version: 8.0.x - name: Add Azure artifact - run: dotnet nuget add source 'https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json' --name 'LearningHubFeed' --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text + run: | + dotnet nuget remove source LearningHubFeed || true + dotnet nuget add source 'https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json' --name 'LearningHubFeed' --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text - name: Use Node 20 with Yarn uses: actions/setup-node@v4 From fccfc4a04ce805c16e4b415d2ca85e646e9e36b2 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Mon, 9 Jun 2025 09:28:36 +0100 Subject: [PATCH 09/83] Updated dashboard event to use the open api --- LearningHub.Nhs.WebUI/Services/DashboardService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 56d132e3b..607d4f2be 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -150,7 +150,7 @@ public async Task RecordDashBoardEventAsync(DashboardEventViewModel dashboardEve }; var content = new System.Net.Http.StringContent(JsonConvert.SerializeObject(eventEntity), Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"event/Create"; var response = await client.PostAsync(request, content).ConfigureAwait(false); From 9f27f21a383b04bb2fc511ee4209dbc09fc55ef3 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Mon, 9 Jun 2025 16:19:43 +0100 Subject: [PATCH 10/83] Internal Sytem db mapping --- .../EntityFramework/ServiceMappings.cs | 2 ++ .../Map/Maintenance/InternalSystemMap.cs | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 87556117f..631359041 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -8,6 +8,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.OpenApi.Repositories.Map.Content; using LearningHub.Nhs.OpenApi.Repositories.Map.External; using LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Map.Messaging; using LearningHub.Nhs.OpenApi.Repositories.Map.Resources; using Microsoft.EntityFrameworkCore; @@ -170,6 +171,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs new file mode 100644 index 000000000..6da03f6fd --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs @@ -0,0 +1,32 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance +{ + using LearningHub.Nhs.Models.Entities.Maintenance; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// Defines the . + /// + public class InternalSystemMap : BaseEntityMap + { + /// + /// The InternalMap. + /// + /// The entity. + protected override void InternalMap(EntityTypeBuilder entity) + { + entity.ToTable("InternalSystem", "maintenance"); + + entity.HasIndex(e => e.Name) + .IsUnique(); + + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(50); + + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(2000); + } + } +} From 6766426052630f86c798aeeeb048453511a3f0af Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 11 Jun 2025 09:54:53 +0100 Subject: [PATCH 11/83] azure appsetting config --- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 16ddd85a9..3b4fe5ae9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -45,7 +45,8 @@ "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" - } + }, + "AzureStorageQueueConnectionString": "" }, "FindWise": { "SearchBaseUrl": "", From be135d46f6a007c2d22f49fe074f0ce122dc0e02 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Fri, 13 Jun 2025 11:33:27 +0100 Subject: [PATCH 12/83] TD-5003: Issue showing 1 sec delay when played Video/Audio resources on 'My Learning' page --- .../Helpers/LearningActivityHelper.cs | 20 +++++++++++++++++-- .../Helpers/ViewActivityHelper.cs | 20 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs index caffaaa5d..b67989350 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs @@ -85,7 +85,15 @@ public static string GetResourceTypeVerb(this MyLearningDetailedItemViewModel my case ResourceTypeEnum.Article: return "Read"; case ResourceTypeEnum.Audio: - return "Played " + GetDurationText(myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000); + if ((myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000) > myLearningDetailedItemViewModel.ResourceDurationMilliseconds) + { + return "Played " + GetDurationText(myLearningDetailedItemViewModel.ResourceDurationMilliseconds); + } + else + { + return "Played " + GetDurationText(myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000); + } + case ResourceTypeEnum.Embedded: return string.Empty; case ResourceTypeEnum.Equipment: @@ -113,7 +121,15 @@ public static string GetResourceTypeVerb(this MyLearningDetailedItemViewModel my } case ResourceTypeEnum.Video: - return "Played " + GetDurationText(myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000); + if ((myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000) > myLearningDetailedItemViewModel.ResourceDurationMilliseconds) + { + return "Played " + GetDurationText(myLearningDetailedItemViewModel.ResourceDurationMilliseconds); + } + else + { + return "Played " + GetDurationText(myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000); + } + case ResourceTypeEnum.WebLink: return "Visited"; case ResourceTypeEnum.Html: diff --git a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs index 8d17307cf..f3bd9259c 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs @@ -84,7 +84,15 @@ public static string GetResourceTypeVerb(this ActivityDetailedItemViewModel acti case ResourceTypeEnum.Article: return "Read"; case ResourceTypeEnum.Audio: - return "Played " + GetDurationText(activityDetailedItemViewModel.ActivityDurationSeconds * 1000); + if ((activityDetailedItemViewModel.ActivityDurationSeconds * 1000) > activityDetailedItemViewModel.ResourceDurationMilliseconds) + { + return "Played " + GetDurationText(activityDetailedItemViewModel.ResourceDurationMilliseconds); + } + else + { + return "Played " + GetDurationText(activityDetailedItemViewModel.ActivityDurationSeconds * 1000); + } + case ResourceTypeEnum.Embedded: return string.Empty; case ResourceTypeEnum.Equipment: @@ -112,7 +120,15 @@ public static string GetResourceTypeVerb(this ActivityDetailedItemViewModel acti } case ResourceTypeEnum.Video: - return "Played " + GetDurationText(activityDetailedItemViewModel.ActivityDurationSeconds * 1000); + if ((activityDetailedItemViewModel.ActivityDurationSeconds * 1000) > activityDetailedItemViewModel.ResourceDurationMilliseconds) + { + return "Played " + GetDurationText(activityDetailedItemViewModel.ResourceDurationMilliseconds); + } + else + { + return "Played " + GetDurationText(activityDetailedItemViewModel.ActivityDurationSeconds * 1000); + } + case ResourceTypeEnum.WebLink: return "Visited"; case ResourceTypeEnum.Html: From f15174a47d89d1023bf78b73e8f8ed2d9a6a6c2b Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 13 Jun 2025 16:24:38 +0100 Subject: [PATCH 13/83] updatiing the nuget.config file --- LearningHub.Nhs.WebUI.sln | 2 ++ LearningHub.Nhs.WebUI/web.config | 41 +++++++++++++++------------ WebAPI/LearningHub.Nhs.API/web.config | 12 +++++--- nuget.config | 2 ++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/LearningHub.Nhs.WebUI.sln b/LearningHub.Nhs.WebUI.sln index 5aea6885f..433e58877 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -8,6 +8,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B5D48B6A-D4A7-494E-89C0-64428232D242}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + nuget.config = nuget.config StyleCop.ruleset = StyleCop.ruleset EndProjectSection EndProject diff --git a/LearningHub.Nhs.WebUI/web.config b/LearningHub.Nhs.WebUI/web.config index 837247997..541f29920 100644 --- a/LearningHub.Nhs.WebUI/web.config +++ b/LearningHub.Nhs.WebUI/web.config @@ -1,21 +1,26 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.API/web.config b/WebAPI/LearningHub.Nhs.API/web.config index 62efcf17d..6483ee683 100644 --- a/WebAPI/LearningHub.Nhs.API/web.config +++ b/WebAPI/LearningHub.Nhs.API/web.config @@ -11,10 +11,14 @@ - - - - + + + + + + + + diff --git a/nuget.config b/nuget.config index 4b5a05544..e612356f4 100644 --- a/nuget.config +++ b/nuget.config @@ -13,6 +13,8 @@ + + \ No newline at end of file From de8cc48fae070b7e266f5995b869b05bb5e16144 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Thu, 12 Jun 2025 17:18:01 +0100 Subject: [PATCH 14/83] image annotation mapping --- .../EntityFramework/ServiceMappings.cs | 2 ++ ...AnnotationMap.cs => ImageAnnotationMap.cs} | 14 +++++--- ...onMarkMap.cs => ImageAnnotationMarkMap.cs} | 8 ++--- .../Resources/Blocks/ImageCarouselBlockMap.cs | 36 +++++++++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) rename OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/{WholeSlideImageAnnotationMap.cs => ImageAnnotationMap.cs} (65%) rename OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/{WholeSlideImageAnnotationMarkMap.cs => ImageAnnotationMarkMap.cs} (80%) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 631359041..18e035f8b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -11,6 +11,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Map.Messaging; using LearningHub.Nhs.OpenApi.Repositories.Map.Resources; + using LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -80,6 +81,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs similarity index 65% rename from OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs index 00f7f46cb..fd31afb51 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs @@ -1,20 +1,18 @@ -namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks { using LearningHub.Nhs.Models.Entities.Resource; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; /// - /// The whole slide image annotation map. + /// The image annotation map. /// public class ImageAnnotationMap : BaseEntityMap { /// /// The internal map. /// - /// - /// The model builder. - /// + /// The model builder. protected override void InternalMap(EntityTypeBuilder modelBuilder) { modelBuilder.ToTable("ImageAnnotation", "resources"); @@ -24,6 +22,12 @@ protected override void InternalMap(EntityTypeBuilder modelBuil .HasForeignKey(a => a.WholeSlideImageId) .OnDelete(DeleteBehavior.Cascade) .HasConstraintName("FK_ImageAnnotation_WholeSlideImageId"); + + modelBuilder.HasOne(a => a.Image) + .WithMany(i => i.ImageAnnotations) + .HasForeignKey(a => a.ImageId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageAnnotation_ImageId"); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs similarity index 80% rename from OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs index 9db0a6db6..a1bad42e9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs @@ -1,20 +1,18 @@ -namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks { using LearningHub.Nhs.Models.Entities.Resource; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; /// - /// The whole slide image annotation map. + /// The image annotation mark map. /// public class ImageAnnotationMarkMap : BaseEntityMap { /// /// The internal map. /// - /// - /// The model builder. - /// + /// The model builder. protected override void InternalMap(EntityTypeBuilder modelBuilder) { modelBuilder.ToTable("ImageAnnotationMark", "resources"); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs new file mode 100644 index 000000000..706269026 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks +{ + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// the image carousel block map. + /// + public class ImageCarouselBlockMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("ImageCarouselBlock", "resources"); + + modelBuilder.Property(e => e.BlockId).ValueGeneratedNever(); + modelBuilder.HasAlternateKey(c => c.BlockId); + + modelBuilder.HasOne(d => d.Block) + .WithOne(p => p.ImageCarouselBlock) + .HasForeignKey(d => d.BlockId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageCarouselBlock_BlockId"); + + modelBuilder.HasOne(d => d.ImageBlockCollection) + .WithOne() + .HasForeignKey(d => d.ImageBlockCollectionId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageCarouselBlock_ImageBlockCollectionId"); + } + } +} \ No newline at end of file From 6894891ae4cb0081251c69f6a460e313b66cf5bf Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Fri, 13 Jun 2025 14:40:50 +0100 Subject: [PATCH 15/83] appsettings update --- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 3b4fe5ae9..804dc13f5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -49,16 +49,21 @@ "AzureStorageQueueConnectionString": "" }, "FindWise": { + "IndexUrl": "", "SearchBaseUrl": "", "CollectionIds": { "Resource": "hee-test", - "Catalogue": "", - "AutoSuggestion": "" + "Catalogue": "catalogues-test", + "AutoSuggestion": "auto-suggestion-test" }, "SearchEndpointPath": "rest/apps/HEE/searchers/", + "UrlClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click", + "UrlAutoSuggestionClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click-hee", "Token": "", "DefaultItemLimitForSearch": 10, "IndexMethod": "/rest/v2/collections/{0}/documents", + "DescriptionLengthLimit": 3000, + "MaximumDescriptionLength": 150, "SpecialSearchCharacters": "#:&()/-[]%" }, "LearningHub": { From 55acd9f689385a1c99b548bd9a10d94507ba1bc8 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 16 Jun 2025 15:59:11 +0100 Subject: [PATCH 16/83] TD-5665-Adding resources to the community catalogue is suspended --- .../Interfaces/IUserGroupService.cs | 6 ++++++ .../Services/NavigationPermissionService.cs | 13 +++++++++---- .../Services/UserGroupService.cs | 17 ++++++++++++++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs index a333e0c89..c91c2e0b2 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs @@ -22,6 +22,12 @@ public interface IUserGroupService /// The . Task> GetRoleUserGroupDetailForUserAsync(int userId); + /// + /// The GetRoleUserGroupDetailAsync. + /// + /// The . + Task UserHasCatalogueContributionPermission(); + /// /// Check if user has given permission. /// diff --git a/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs b/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs index 95e74022e..e381b0403 100644 --- a/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs +++ b/LearningHub.Nhs.WebUI/Services/NavigationPermissionService.cs @@ -11,14 +11,19 @@ public class NavigationPermissionService : INavigationPermissionService { private readonly IResourceService resourceService; + private readonly IUserGroupService userGroupService; /// /// Initializes a new instance of the class. /// /// Resource service. - public NavigationPermissionService(IResourceService resourceService) + /// UserGroup service. + public NavigationPermissionService( + IResourceService resourceService, + IUserGroupService userGroupService) { this.resourceService = resourceService; + this.userGroupService = userGroupService; } /// @@ -52,7 +57,7 @@ public async Task GetNavigationModelAsync(IPrincipal user, bool } else if (user.IsInRole("BlueUser")) { - return this.AuthenticatedBlueUser(controllerName); + return await this.AuthenticatedBlueUser(controllerName); } else { @@ -114,11 +119,11 @@ private NavigationModel AuthenticatedAdministrator(string controllerName) /// /// The controller name. /// The . - private NavigationModel AuthenticatedBlueUser(string controllerName) + private async Task AuthenticatedBlueUser(string controllerName) { return new NavigationModel() { - ShowMyContributions = true, + ShowMyContributions = await this.userGroupService.UserHasCatalogueContributionPermission(), ShowMyLearning = true, ShowMyBookmarks = true, ShowSearch = controllerName != "search" && controllerName != string.Empty, diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 6790a3143..be77f6531 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -46,7 +46,10 @@ public UserGroupService( public async Task> GetRoleUserGroupDetailAsync() { var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; - return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); + cacheKey = null; + ////return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); + + return await this.FetchRoleUserGroupDetailAsync(); } /// @@ -56,6 +59,18 @@ public async Task> GetRoleUserGroupDetailForUserAsy return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailForUserAsync(userId)); } + /// + public async Task UserHasCatalogueContributionPermission() + { + var userRoleGroups = await this.GetRoleUserGroupDetailAsync(); + if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleName == "Local Admin" || r.RoleName == "Editor")) + { + return true; + } + + return false; + } + /// public async Task UserHasPermissionAsync(string permissionCode) { From 0bd9e91b7a1562101830f238bff12b6e35bfbbd4 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 16 Jun 2025 16:59:56 +0100 Subject: [PATCH 17/83] Td-5664: Implement a business rule tieing 'Contribute a resource' and a Catalogue permission/group --- .gitignore | 3 +++ AdminUI/LearningHub.Nhs.AdminUI/web.config | 21 --------------------- LearningHub.Nhs.WebUI/web.config | 21 --------------------- WebAPI/LearningHub.Nhs.API/web.config | 21 --------------------- 4 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 AdminUI/LearningHub.Nhs.AdminUI/web.config delete mode 100644 LearningHub.Nhs.WebUI/web.config delete mode 100644 WebAPI/LearningHub.Nhs.API/web.config diff --git a/.gitignore b/.gitignore index fcd06e014..8cc7d8bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ obj /AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user /WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user /ReportAPI/LearningHub.Nhs.ReportApi/web.config +/AdminUI/LearningHub.Nhs.AdminUI/web.config +/LearningHub.Nhs.WebUI/web.config +/WebAPI/LearningHub.Nhs.API/web.config diff --git a/AdminUI/LearningHub.Nhs.AdminUI/web.config b/AdminUI/LearningHub.Nhs.AdminUI/web.config deleted file mode 100644 index 6bcb74972..000000000 --- a/AdminUI/LearningHub.Nhs.AdminUI/web.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/web.config b/LearningHub.Nhs.WebUI/web.config deleted file mode 100644 index 837247997..000000000 --- a/LearningHub.Nhs.WebUI/web.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/WebAPI/LearningHub.Nhs.API/web.config b/WebAPI/LearningHub.Nhs.API/web.config deleted file mode 100644 index 62efcf17d..000000000 --- a/WebAPI/LearningHub.Nhs.API/web.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 4ec4010ce9ad18ef671129196990299e7ab24ff3 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 16 Jun 2025 17:00:18 +0100 Subject: [PATCH 18/83] TD-5664: Implement a business rule tieing 'Contribute a resource' and a Catalogue permission/group --- .../Hierarchy/CatalogueNodeVersionRepository.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs index 38f77a37f..d12f1b730 100644 --- a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs @@ -81,10 +81,10 @@ public async Task> GetPublishedCatalogues() /// The . public IQueryable GetPublishedCataloguesForUserAsync(int userId) { - var communityCatalogue = this.DbContext.CatalogueNodeVersion.AsNoTracking() - .Include(cnv => cnv.NodeVersion.Node) - .Where(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published - && cnv.NodeVersion.NodeId == 1 /* Community Catalogue */); + ////var communityCatalogue = this.DbContext.CatalogueNodeVersion.AsNoTracking() + //// .Include(cnv => cnv.NodeVersion.Node) + //// .Where(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published + //// && cnv.NodeVersion.NodeId == 1 /* Community Catalogue */); var cataloguesForUser = from cnv in this.DbContext.CatalogueNodeVersion.Include(cnv => cnv.NodeVersion.Node).AsNoTracking() join nv in this.DbContext.NodeVersion.Where(cnv => cnv.VersionStatusEnum == VersionStatusEnum.Published && !cnv.Deleted) // .Include(nv => nv.Node) @@ -99,7 +99,7 @@ join n in this.DbContext.Node.Where(x => !x.Deleted) on nv.Id equals n.CurrentNodeVersionId select cnv; - var returnedCatalogues = communityCatalogue.Union(cataloguesForUser).Distinct() + var returnedCatalogues = cataloguesForUser.Distinct() .OrderBy(cnv => cnv.NodeVersion.NodeId != 1) .ThenBy(cnv => cnv.Name); From 4e100304432fa253bfcb6a96c44fa30da5d56667 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 16 Jun 2025 17:07:20 +0100 Subject: [PATCH 19/83] Removed commented lines --- .../Hierarchy/CatalogueNodeVersionRepository.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs index d12f1b730..5ae373e42 100644 --- a/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/WebAPI/LearningHub.Nhs.Repository/Hierarchy/CatalogueNodeVersionRepository.cs @@ -81,11 +81,6 @@ public async Task> GetPublishedCatalogues() /// The . public IQueryable GetPublishedCataloguesForUserAsync(int userId) { - ////var communityCatalogue = this.DbContext.CatalogueNodeVersion.AsNoTracking() - //// .Include(cnv => cnv.NodeVersion.Node) - //// .Where(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published - //// && cnv.NodeVersion.NodeId == 1 /* Community Catalogue */); - var cataloguesForUser = from cnv in this.DbContext.CatalogueNodeVersion.Include(cnv => cnv.NodeVersion.Node).AsNoTracking() join nv in this.DbContext.NodeVersion.Where(cnv => cnv.VersionStatusEnum == VersionStatusEnum.Published && !cnv.Deleted) // .Include(nv => nv.Node) on cnv.NodeVersionId equals nv.Id From 2b436720b813385475835c8c832e08a79a38a1f8 Mon Sep 17 00:00:00 2001 From: binon Date: Mon, 16 Jun 2025 17:38:27 +0100 Subject: [PATCH 20/83] Update nuget.config --- nuget.config | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nuget.config b/nuget.config index e612356f4..33abf6d35 100644 --- a/nuget.config +++ b/nuget.config @@ -12,9 +12,9 @@ - - - + + + - \ No newline at end of file + From 7d8e96ee0845a289b44c06783dd9d8bda90f7c53 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 17 Jun 2025 11:11:17 +0100 Subject: [PATCH 21/83] Removed the commented lines --- LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs | 2 +- LearningHub.Nhs.WebUI/Services/UserGroupService.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs index c91c2e0b2..eadae3363 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserGroupService.cs @@ -23,7 +23,7 @@ public interface IUserGroupService Task> GetRoleUserGroupDetailForUserAsync(int userId); /// - /// The GetRoleUserGroupDetailAsync. + /// The UserHasCatalogueContributionPermission. /// /// The . Task UserHasCatalogueContributionPermission(); diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index be77f6531..826aa07a0 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -45,11 +45,8 @@ public UserGroupService( /// public async Task> GetRoleUserGroupDetailAsync() { - var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; - cacheKey = null; - ////return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); - - return await this.FetchRoleUserGroupDetailAsync(); + var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; + return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); } /// From 4e1d0657aa04f1176ae77c46f6c3306b32d2f3ae Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 17 Jun 2025 12:02:01 +0100 Subject: [PATCH 22/83] TD-5663: Post log in dashboard - remove banner promoting contribute a resource --- LearningHub.Nhs.WebUI/Controllers/HomeController.cs | 10 ++++++++-- LearningHub.Nhs.WebUI/Services/UserGroupService.cs | 5 +++-- LearningHub.Nhs.WebUI/Views/Home/Dashboard.cshtml | 10 +++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 7f40afe15..18e3b82fe 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -40,6 +40,7 @@ public class HomeController : BaseController private readonly IDashboardService dashboardService; private readonly IContentService contentService; private readonly IFeatureManager featureManager; + private readonly IUserGroupService userGroupService; private readonly Microsoft.Extensions.Configuration.IConfiguration configuration; /// @@ -55,6 +56,7 @@ public class HomeController : BaseController /// Dashboard service. /// Content service. /// featureManager. + /// userGroupService. /// config. public HomeController( IHttpClientFactory httpClientFactory, @@ -67,6 +69,7 @@ public HomeController( IDashboardService dashboardService, IContentService contentService, IFeatureManager featureManager, + IUserGroupService userGroupService, Microsoft.Extensions.Configuration.IConfiguration configuration) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { @@ -76,6 +79,7 @@ public HomeController( this.dashboardService = dashboardService; this.contentService = contentService; this.featureManager = featureManager; + this.userGroupService = userGroupService; this.configuration = configuration; } @@ -212,6 +216,7 @@ public async Task Index(string myLearningDashboard = "my-in-progr var learningTask = this.dashboardService.GetMyAccessLearningsAsync(myLearningDashboard, 1); var resourcesTask = this.dashboardService.GetResourcesAsync(resourceDashboard, 1); var cataloguesTask = this.dashboardService.GetCataloguesAsync(catalogueDashboard, 1); + var userGroupsTask = this.userGroupService.UserHasCatalogueContributionPermission(); var enrolledCoursesTask = Task.FromResult(new List()); var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; @@ -222,7 +227,7 @@ public async Task Index(string myLearningDashboard = "my-in-progr enrolledCoursesTask = this.dashboardService.GetEnrolledCoursesFromMoodleAsync(this.CurrentMoodleUserId, 1); } - await Task.WhenAll(learningTask, resourcesTask, cataloguesTask); + await Task.WhenAll(learningTask, resourcesTask, cataloguesTask, userGroupsTask); var model = new DashboardViewModel() { @@ -231,7 +236,8 @@ public async Task Index(string myLearningDashboard = "my-in-progr Catalogues = await cataloguesTask, EnrolledCourses = await enrolledCoursesTask, }; - + var userHasContributePermission = await userGroupsTask; + this.ViewBag.userHasContributePermission = userHasContributePermission; if (!string.IsNullOrEmpty(this.Request.Query["preview"]) && Convert.ToBoolean(this.Request.Query["preview"])) { return this.View("LandingPage", await this.GetLandingPageContent(Convert.ToBoolean(this.Request.Query["preview"]))); diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 826aa07a0..6a4f62966 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Interfaces; @@ -45,7 +46,7 @@ public UserGroupService( /// public async Task> GetRoleUserGroupDetailAsync() { - var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; + var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); } @@ -60,7 +61,7 @@ public async Task> GetRoleUserGroupDetailForUserAsy public async Task UserHasCatalogueContributionPermission() { var userRoleGroups = await this.GetRoleUserGroupDetailAsync(); - if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleName == "Local Admin" || r.RoleName == "Editor")) + if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.LocalAdmin || r.RoleEnum == RoleEnum.Editor)) { return true; } diff --git a/LearningHub.Nhs.WebUI/Views/Home/Dashboard.cshtml b/LearningHub.Nhs.WebUI/Views/Home/Dashboard.cshtml index 4a7b55c08..e8481a4c7 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/Dashboard.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/Dashboard.cshtml @@ -5,7 +5,15 @@ @{ ViewData["Title"] = "Learning Hub - Home"; - var isReadOnly = User.IsInRole("ReadOnly") || User.IsInRole("BasicUser"); + var isReadOnly = false; + if (User.IsInRole("ReadOnly") || User.IsInRole("BasicUser")) + { + isReadOnly = true; + } + else if (User.IsInRole("BlueUser") && !this.ViewBag.userHasContributePermission) + { + isReadOnly = true; + } } @section styles { From 8a84fc0d4fe7e34d058d5b07cd50ef4943890b93 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 12:20:48 +0100 Subject: [PATCH 23/83] Additional Updates --- LearningHub.Nhs.WebUI/Services/CardService.cs | 8 +- .../Services/CatalogueService.cs | 36 +-- .../Services/ContributeService.cs | 28 +- .../Services/DashboardService.cs | 2 +- .../Services/ProviderService.cs | 6 +- .../Services/UserGroupService.cs | 4 +- LearningHub.Nhs.WebUI/Services/UserService.cs | 30 +- .../Configuration/LearningHubConfig.cs | 21 ++ .../IUserPasswordResetRequestsRepository.cs | 31 ++ .../EntityFramework/LearningHubDbContext.cs | 5 + .../EntityFramework/ServiceMappings.cs | 2 + .../Map/PasswordResetRequestsMap.cs | 23 ++ .../UserPasswordResetRequestsRepository.cs | 84 +++++ .../Startup.cs | 1 + .../Services/IResourceService.cs | 24 ++ .../IUserPasswordResetRequestsService.cs | 26 ++ .../Services/ResourceService.cs | 294 ++++++++++++++++++ .../UserPasswordResetRequestsService.cs | 80 +++++ .../Services/UserProfileService.cs | 4 +- .../Startup.cs | 1 + .../AuthorizeOrCallFromLHHandler.cs | 87 ++++++ .../AuthorizeOrCallFromLHRequirement.cs | 18 ++ .../Controllers/ResourceController.cs | 36 +++ .../Controllers/UserController.cs | 36 +++ OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs | 8 +- 25 files changed, 835 insertions(+), 60 deletions(-) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserPasswordResetRequestsRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/PasswordResetRequestsMap.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserPasswordResetRequestsRepository.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserPasswordResetRequestsService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserPasswordResetRequestsService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHHandler.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHRequirement.cs diff --git a/LearningHub.Nhs.WebUI/Services/CardService.cs b/LearningHub.Nhs.WebUI/Services/CardService.cs index 12b312b07..d3aac0616 100644 --- a/LearningHub.Nhs.WebUI/Services/CardService.cs +++ b/LearningHub.Nhs.WebUI/Services/CardService.cs @@ -35,7 +35,7 @@ public async Task GetMyContributionsTotalsAsync( { MyContributionsTotalsViewModel totals = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetMyContributionsTotals/{catalogueId.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -64,7 +64,7 @@ public async Task> GetContributionsAsync( { List myContributionCards = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(resourceContributionsRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -95,7 +95,7 @@ public async Task GetMyResourceViewModelAsync() { MyResourceViewModel myresourcecards = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetMyResourceViewModel"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -124,7 +124,7 @@ public async Task GetResourceCardExtendedViewMode { ResourceCardExtendedViewModel resourceCardExtendedViewModel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/ResourceCardExtendedViewModel/{id.ToString()}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs index d36057194..3833a4e28 100644 --- a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs @@ -42,7 +42,7 @@ public async Task> GetCataloguesForUserAsync() { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Catalogue/GetForCurrentUser"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -99,7 +99,7 @@ public async Task GetCatalogueAsync(int catalogueNodeVersion { CatalogueViewModel viewmodel = new CatalogueViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"catalogue/catalogues/{catalogueNodeVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -127,7 +127,7 @@ public async Task GetCatalogueRecordedAsync(string reference { CatalogueViewModel viewmodel = new CatalogueViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"catalogue/catalogue-recorded/{reference}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -155,7 +155,7 @@ public async Task GetResourcesAsync(Catalogu { CatalogueResourceResponseViewModel viewmodel = new CatalogueResourceResponseViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var json = JsonConvert.SerializeObject(requestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); @@ -186,7 +186,7 @@ public async Task CanCurrentUserEditCatalogue(int catalogueId) { var request = $"Catalogue/CanCurrentUserEditCatalogue/{catalogueId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync(request).ConfigureAwait(false); var catalogueIsEditable = false; @@ -214,7 +214,7 @@ public async Task AccessDetailsAsync(string ref { var request = $"Catalogue/AccessDetails/{reference}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync(request).ConfigureAwait(false); var accessDetails = new CatalogueAccessDetailsViewModel(); @@ -242,7 +242,7 @@ public async Task GetLatestCatalogueAccessReque { var request = $"Catalogue/GetLatestCatalogueAccessRequest/{catalogueNodeId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync(request).ConfigureAwait(false); var car = new CatalogueAccessRequestViewModel(); @@ -276,7 +276,7 @@ public async Task RequestAccessAsync(string referen { var request = $"Catalogue/RequestAccess/{reference}/{accessType}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(vm), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); var catalogueAccessRequested = false; @@ -309,7 +309,7 @@ public async Task InviteUserAsync(RestrictedCatalog { var request = $"Catalogue/InviteUser"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(vm), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); var catalogueAccessRequested = false; @@ -345,7 +345,7 @@ public async Task> GetRestricted var json = JsonConvert.SerializeObject(requestModel); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Catalogue/GetRestrictedCatalogueAccessRequests"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -378,7 +378,7 @@ public async Task GetRestrictedCatalogueSum { var viewmodel = new RestrictedCatalogueSummaryViewModel(); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"catalogue/GetRestrictedCatalogueSummary/{catalogueNodeId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -413,7 +413,7 @@ public async Task GetRestrictedCatalogueUsers var json = JsonConvert.SerializeObject(requestModel); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Catalogue/GetRestrictedCatalogueUsers"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -447,7 +447,7 @@ public async Task AcceptAccessRequestAsync(int acce { var request = $"Catalogue/AcceptAccessRequest/{accessRequestId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(new { })); var response = await client.PostAsync(request, content).ConfigureAwait(false); var catalogueAccessRequested = new LearningHubValidationResult(); @@ -479,7 +479,7 @@ public async Task RejectAccessRequestAsync(int acce { var request = $"Catalogue/RejectAccessRequest/{accessRequestId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(new CatalogueAccessRejectionViewModel { RejectionReason = rejectionReason }), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); var vr = new LearningHubValidationResult(); @@ -508,7 +508,7 @@ public async Task DismissAccessRequestAsync(int cat { var request = $"Catalogue/DismissAccessRequest/{catalogueNodeId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var content = new StringContent(JsonConvert.SerializeObject(new { })); var response = await client.PostAsync(request, content).ConfigureAwait(false); var vr = new LearningHubValidationResult(false); @@ -537,7 +537,7 @@ public async Task GetCatalogueAccessRequestAsyn { var request = $"Catalogue/AccessRequest/{catalogueAccessRequestId}"; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var response = await client.GetAsync(request).ConfigureAwait(false); var catalogueAccessRequest = new CatalogueAccessRequestViewModel(); @@ -571,7 +571,7 @@ public async Task RemoveUserFromRestrictedAccessUse var json = JsonConvert.SerializeObject(userGroup); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/DeleteUserUserGroup"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -612,7 +612,7 @@ public async Task RemoveUserFromRestrictedAccessUse public async Task GetAllCatalogueAsync(string filterChar) { AllCatalogueResponseViewModel viewmodel = new AllCatalogueResponseViewModel { }; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"catalogue/allcatalogues/{filterChar}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/ContributeService.cs b/LearningHub.Nhs.WebUI/Services/ContributeService.cs index 1638e1ef1..68a2b4cf6 100644 --- a/LearningHub.Nhs.WebUI/Services/ContributeService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContributeService.cs @@ -357,7 +357,7 @@ public async Task GetFileChunkDetail(int fileChunkDeta { FileChunkDetailViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/GetFileChunkDetail/{fileChunkDetailId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -800,7 +800,7 @@ public async Task SaveArticleAttachedFileDetailsAsync(FileCreateRequestView var json = JsonConvert.SerializeObject(fileCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SaveArticleAttachedFileDetails"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -834,7 +834,7 @@ public async Task SaveArticleDetailAsync(ArticleUpdateRequestViewModel arti var json = JsonConvert.SerializeObject(articleViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateArticleDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -868,7 +868,7 @@ public async Task SaveFileChunkDetailsAsync(FileChunkDetailViewModel fileCh var json = JsonConvert.SerializeObject(fileChunkDetailCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SaveFileChunkDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -902,7 +902,7 @@ public async Task SaveFileDetailsAsync(FileCreateRequestViewModel fileCreat var json = JsonConvert.SerializeObject(fileCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SaveFileDetails"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -936,7 +936,7 @@ public async Task SaveGenericFileDetailAsync(GenericFileUpdateRequestViewMo var json = JsonConvert.SerializeObject(genericFileViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateGenericFileDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -970,7 +970,7 @@ public async Task SaveScormDetailAsync(ScormUpdateRequestViewModel scormUpd var json = JsonConvert.SerializeObject(scormUpdateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateScormDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1004,7 +1004,7 @@ public async Task SaveHtmlDetailAsync(HtmlResourceUpdateRequestViewModel ht var json = JsonConvert.SerializeObject(htmlResource); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateHtmlDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1038,7 +1038,7 @@ public async Task SaveImageDetailAsync(ImageUpdateRequestViewModel imageVie var json = JsonConvert.SerializeObject(imageViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateImageDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1072,7 +1072,7 @@ public async Task SaveResourceAttributeFileDetailsAsync(FileCreateRequestVi var json = JsonConvert.SerializeObject(fileCreateRequestViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SaveResourceAttributeFileDetails"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1129,7 +1129,7 @@ public async Task SaveWeblinkDetailAsync(WebLinkViewModel weblinkViewModel) var json = JsonConvert.SerializeObject(weblinkViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateWeblinkDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1163,7 +1163,7 @@ public async Task SaveCaseDetailAsync(CaseViewModel caseViewModel) var json = JsonConvert.SerializeObject(caseViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateCaseDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1197,7 +1197,7 @@ public async Task SaveAssessmentDetailAsync(AssessmentViewModel assessmentV var json = JsonConvert.SerializeObject(assessmentViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/UpdateAssessmentDetail"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1231,7 +1231,7 @@ public async Task SubmitResourceVersionForPublishAs var json = JsonConvert.SerializeObject(publishViewModel); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Resource/SubmitResourceVersionForPublish"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 607d4f2be..24dabdfef 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -48,7 +48,7 @@ public async Task GetMyAccessLearningsAsyn var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"dashboard/myaccesslearning/{dashboardType}/{pageNumber}"; - var response = await client.GetAsync(request).ConfigureAwait(false); + var response = await client.GetAsync(request).ConfigureAwait(true); if (response.IsSuccessStatusCode) { diff --git a/LearningHub.Nhs.WebUI/Services/ProviderService.cs b/LearningHub.Nhs.WebUI/Services/ProviderService.cs index daf2c4273..ee6119c60 100644 --- a/LearningHub.Nhs.WebUI/Services/ProviderService.cs +++ b/LearningHub.Nhs.WebUI/Services/ProviderService.cs @@ -48,7 +48,7 @@ public async Task> GetProvidersForUserAsync(int userId) { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Provider/GetProvidersByUserId/{userId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -77,7 +77,7 @@ public async Task> GetProvidersForResourceAsync(int reso { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Provider/GetProvidersByResource/{resourceVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -112,7 +112,7 @@ private async Task> GetAllProviders() { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Provider/all"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index cee569d72..a9a792648 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -78,7 +78,7 @@ private async Task> FetchRoleUserGroupDetailAsync() { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserGroupRoleDetail"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -102,7 +102,7 @@ private async Task> FetchRoleUserGroupDetailForUser { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"UserGroup/GetUserGroupRoleDetailByUserId/{userId}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 98509080b..1dbea7fce 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -96,7 +96,7 @@ public async Task> GetActiveContentAsync() { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "User/GetActiveContent"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -336,7 +336,7 @@ public async Task GetCurrentUserProfileAsync() { UserProfile viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "User/GetCurrentUserProfile"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -361,7 +361,7 @@ public async Task GetUserProfileAsync(int userId) { UserProfile viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/GetUserProfile/{userId}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -389,7 +389,7 @@ public async Task CreateUserProfileAsync(UserProfil var json = JsonConvert.SerializeObject(userProfile); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/CreateUserProfile"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -424,7 +424,7 @@ public async Task UpdateUserProfileAsync(UserProfil var json = JsonConvert.SerializeObject(userProfile); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/UpdateUserProfile"; var response = await client.PutAsync(request, stringContent).ConfigureAwait(false); @@ -625,7 +625,7 @@ public async Task GetLHUserByUserIdAsync(int id) { UserLHBasicViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/GetByUserId/{id}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1096,7 +1096,7 @@ public async Task UpdateUserAsync(UserUpdateViewMod var json = JsonConvert.SerializeObject(userUpdateViewModel); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/UpdateUser"; @@ -1132,7 +1132,7 @@ public async Task CreateUserAsync(UserCreateViewMod var json = JsonConvert.SerializeObject(newLhUser); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/CreateUser"; var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); @@ -1568,7 +1568,7 @@ public async Task ValidateEmailChangeTokenAsyn { EmailChangeValidationTokenResult tokenResult = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/ValidateEmailChangeToken/{Uri.EscapeUriString(token.EncodeParameter())}/{Uri.EscapeUriString(loctoken.EncodeParameter())}/{isUserRoleUpgrade}"; @@ -1597,7 +1597,7 @@ public async Task GetLastIssuedEmailChangeV { EmailChangeValidationTokenViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = "User/GetLastIssuedEmailChangeValidationToken"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1622,7 +1622,7 @@ public async Task RegenerateEmailChangeVali { EmailChangeValidationTokenViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/ReGenerateEmailChangeValidationToken/{newPrimaryEmail}/{isUserRoleUpgrade}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1647,7 +1647,7 @@ public async Task CanRequestPasswordResetAsync(string emailAddress, int pa { bool status = false; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/CanRequestPasswordReset/{emailAddress}/{passwordRequestLimitingPeriod}/{passwordRequestLimit}"; var response = await client.GetAsync(request).ConfigureAwait(false); @@ -1672,7 +1672,7 @@ public async Task GenerateEmailChangeValida { EmailChangeValidationTokenViewModel viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/GenerateEmailChangeValidationTokenAndSendEmail/{emailAddress}/{isUserRoleUpgrade}"; @@ -1696,7 +1696,7 @@ public async Task GenerateEmailChangeValida /// public async Task CancelEmailChangeValidationTokenAsync() { - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"User/CancelEmailChangeValidationToken"; @@ -1846,7 +1846,7 @@ public async Task> GetProvidersByUserIdAsync(int userId) { List viewmodel = null; - var client = await this.LearningHubHttpClient.GetClientAsync(); + var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"Provider/GetProvidersByUserId/{userId}"; var response = await client.GetAsync(request).ConfigureAwait(false); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs index 327a0198b..efc4ba99c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs @@ -137,5 +137,26 @@ public class LearningHubConfig /// public string BrowseCataloguesUrl { get; set; } = null!; + + + /// + /// Gets or sets . + /// + public string AuthClientIdentityKey { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string LHClientIdentityKey { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string ContentServerClientIdentityKey { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string ReportApiClientIdentityKey { get; set; } = null!; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserPasswordResetRequestsRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserPasswordResetRequestsRepository.cs new file mode 100644 index 000000000..2b8aff53a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserPasswordResetRequestsRepository.cs @@ -0,0 +1,31 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using LearningHub.Nhs.Models.Entities; + using System.Threading.Tasks; + + /// + /// The UserPasswordResetRequestsRepository interface. + /// + public interface IUserPasswordResetRequestsRepository : IGenericRepository + { + /// + /// To check user can request a password reset. + /// + /// + /// The lookup. + /// + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + Task CreatePasswordRequests(string emailAddress); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index 47302c970..d055990ce 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -79,6 +79,11 @@ public LearningHubDbContextOptions Options /// public virtual DbSet RoleUserGroup { get; set; } + /// + /// Gets or sets the password requests. + /// + public virtual DbSet PasswordResetRequests { get; set; } + /// /// Gets or sets the email change validation token. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 18e035f8b..2384dc4de 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -77,6 +77,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -138,6 +139,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/PasswordResetRequestsMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/PasswordResetRequestsMap.cs new file mode 100644 index 000000000..7959ba657 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/PasswordResetRequestsMap.cs @@ -0,0 +1,23 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map +{ + using LearningHub.Nhs.Models.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The password reset requests map. + /// + public class PasswordResetRequestsMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// + /// The model builder. + /// + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("PasswordResetRequests", "hub"); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserPasswordResetRequestsRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserPasswordResetRequestsRepository.cs new file mode 100644 index 000000000..66c1cb92a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserPasswordResetRequestsRepository.cs @@ -0,0 +1,84 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The user password reset requests repository. + /// + public class UserPasswordResetRequestsRepository : GenericRepository, IUserPasswordResetRequestsRepository + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The db context. + /// + /// + /// The Timezone offset manager. + /// + public UserPasswordResetRequestsRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// To check user can request a password reset. + /// + /// + /// The lookup. + /// + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + public async Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + // passwordRequestLimitingPeriod is in minutes. + var oneMinuteAgo = DateTime.UtcNow.AddMinutes(-passwordRequestLimitingPeriod); + + // Get the count of password reset requests for the user in the last 1 minute + var recentRequests = await DbContext.PasswordResetRequests + .Where(r => r.EmailAddress == emailAddress && r.RequestTime >= oneMinuteAgo) + .CountAsync(); + + return recentRequests < passwordRequestLimit; + } + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + public async Task CreatePasswordRequests(string emailAddress) + { + try + { + var passwordResetRequests = new PasswordResetRequests + { + EmailAddress = emailAddress, + RequestTime = DateTime.UtcNow, + Deleted = false, + CreateUserId = 4, + CreateDate = DateTime.UtcNow, + AmendUserId = 4, + AmendDate = DateTime.UtcNow, + }; + + await DbContext.PasswordResetRequests.AddAsync(passwordResetRequests); + await DbContext.SaveChangesAsync(); + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs index c8099d1ed..c5c9baef6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs @@ -52,6 +52,7 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index 2b4921579..d71c89701 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -194,6 +194,30 @@ Task GetAssessmentProgress( /// The . MyContributionsTotalsViewModel GetMyContributionTotals(int catalogueId, int userId); + /// + /// The get resource card extended view model async. + /// + /// The resourceVersionId. + /// The userId. + /// The . + Task GetResourceCardExtendedViewModelAsync(int resourceVersionId, int userId); + + /// + /// The get my contributions view model async. + /// + /// The userId. + /// The resourceContributionsRequestViewModel. + /// The readOnly. + /// The . + List GetContributions(int userId, ResourceContributionsRequestViewModel resourceContributionsRequestViewModel, bool readOnly); + + /// + /// The get my resource view model async. + /// + /// The userId. + /// The . + Task GetMyResourceViewModelAsync(int userId); + /// /// The has published resources method. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserPasswordResetRequestsService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserPasswordResetRequestsService.cs new file mode 100644 index 000000000..b2d85e004 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserPasswordResetRequestsService.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + + /// + /// Theuser password resets interface. + /// + public interface IUserPasswordResetRequestsService + { + /// + /// The check user can rtequest password reset async. + /// + /// The user name. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// The . + Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + + /// + /// CreatePasswordRequests. + /// + /// The emailAddress. + /// The . + Task CreateUserPasswordRequest(string emailAddress); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index e7c942996..7102145e5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -4342,5 +4342,299 @@ public async Task UpdateDevIdDetailsAsync(ResourceVersionDevIdViewModel resource { await this.resourceVersionRepository.UpdateDevIdAsync(currentUserId, resourceVersionDevIdViewModel); } + + /// + /// The get resource card extended view model async. + /// + /// The resourceVersionId. + /// The userId. + /// The . + public async Task GetResourceCardExtendedViewModelAsync(int resourceVersionId, int userId) + { + var card = await this.GetResourceCardExtendedDetailsAsync(resourceVersionId, userId); + + return card; + } + + /// + /// The get my contributions view model async. + /// + /// The userId. + /// The requestModel. + /// The readOnly. + /// The . + public List GetContributions(int userId, ResourceContributionsRequestViewModel requestModel, bool readOnly) + { + var retVal = new List(); + + var contributions = this.resourceVersionRepository.GetContributions(userId, requestModel); + + var models = this.mapper.Map>(contributions); + + return models; + } + + /// + /// The get my resource view model async. + /// + /// The userId. + /// The . + public async Task GetMyResourceViewModelAsync(int userId) + { + var retVal = new MyResourceViewModel(); + + var rvs = await this.resourceVersionRepository.GetResourceCards(true); + + foreach (var rv in rvs) + { + var card = new ResourceCardViewModel(); + + await this.PopulateResourceCardViewModel(card, rv, userId); + + retVal.Cards.Add(card); + } + + return retVal; + } + + /// + /// The get resource card extended details async. + /// + /// The resourceVersionId. + /// The userId. + /// The . + private async Task GetResourceCardExtendedDetailsAsync(int resourceVersionId, int userId) + { + var card = new ResourceCardExtendedViewModel(); + + var rv = await this.resourceVersionRepository.GetByIdAsync(resourceVersionId, true); + + if (rv != null) + { + await this.PopulateResourceCardViewModel(card, rv, userId); + + var rr = await this.resourceReferenceRepository.GetDefaultByResourceIdAsync(rv.ResourceId); + if (rr != null) + { + card.ResourceReferenceId = rr.Id; + } + + switch (rv.Resource.ResourceTypeEnum) + { + case ResourceTypeEnum.Article: + + var articleDetails = await this.GetArticleDetailsByIdAsync(rv.Id); + card.ArticleDetails = articleDetails; + break; + + case ResourceTypeEnum.Equipment: + + var equipmentDetails = await this.GetEquipmentDetailsByIdAsync(rv.Id); + card.EquipmentDetails = equipmentDetails; + break; + + case ResourceTypeEnum.WebLink: + + var weblinkDetails = await this.GetWebLinkDetailsByIdAsync(rv.Id); + card.WebLinkDetails = weblinkDetails; + break; + + case ResourceTypeEnum.GenericFile: + + var genericFileDetails = await this.GetGenericFileDetailsByIdAsync(rv.Id); + card.GenericFileDetails = genericFileDetails; + break; + + case ResourceTypeEnum.Image: + + var imageDetails = await this.GetImageDetailsByIdAsync(rv.Id); + card.ImageDetails = imageDetails; + break; + + case ResourceTypeEnum.Embedded: + + var embedDetails = await this.GetEmbeddedResourceVersionByIdAsync(rv.Id); + card.EmbedCodeDetails = embedDetails; + break; + + case ResourceTypeEnum.Video: + + var videoDetails = await this.GetVideoDetailsByIdAsync(rv.Id); + card.VideoDetails = videoDetails; + break; + + case ResourceTypeEnum.Audio: + + var audioDetails = await this.GetAudioDetailsByIdAsync(rv.Id); + card.AudioDetails = audioDetails; + break; + + default: + break; + } + } + + return card; + } + + /// + /// The populate resource version card summary. + /// + /// The card. + /// The resourceVersion. + /// The userId. + /// The . + private async Task PopulateResourceCardViewModel(ResourceCardViewModel card, ResourceVersion resourceVersion, int userId) + { + await this.PopulateResourceVersionViewModel(card, resourceVersion); + + card.Id = resourceVersion.Id; + + // Set flags related to the previous published version when the card status is "Draft" or "Publishing" + // "InEdit" and "PreviousPublishedStatusEnum" indication + if (resourceVersion.Resource.CurrentResourceVersionId.HasValue && + (resourceVersion.VersionStatusEnum == VersionStatusEnum.Draft + || resourceVersion.VersionStatusEnum == VersionStatusEnum.Publishing)) + { + if (resourceVersion.VersionStatusEnum == VersionStatusEnum.Draft) + { + card.InEdit = true; + } + + card.PreviousPublishedStatusEnum = resourceVersion.Resource.CurrentResourceVersion.VersionStatusEnum; + if (card.PreviousPublishedStatusEnum == VersionStatusEnum.Unpublished) + { + var previousEvents = this.resourceVersionEventRepository.GetByResourceVersionIdAsync(resourceVersion.Resource.CurrentResourceVersionId.Value); + var unpublishEvent = previousEvents.Where(e => e.ResourceVersionEventType == ResourceVersionEventTypeEnum.Unpublished).FirstOrDefault(); + if (unpublishEvent != null) + { + var u = await this.userProfileService.GetByIdAsync(unpublishEvent.CreateUserId); + card.UnpublishedByAdmin = this.userService.IsAdminUser(u.Id) && u.Id != userId; + card.UnpublishedBy = $"{u.UserName}"; + card.UnpublishedDate = unpublishEvent.CreateDate.DateTime; + } + } + } + + // Created details + var cu = await this.userProfileService.GetByIdAsync(resourceVersion.CreateUserId); + card.CreatedBy = $"{cu.FirstName} {cu.LastName}"; + card.CreatedDate = resourceVersion.CreateDate.DateTime; + + // Unpublished details + if (resourceVersion.VersionStatusEnum == VersionStatusEnum.Unpublished) + { + var unpublishEvent = resourceVersion.ResourceVersionEvent.Where(e => e.ResourceVersionEventType == ResourceVersionEventTypeEnum.Unpublished) + .OrderByDescending(e => e.Id) + .FirstOrDefault(); + if (unpublishEvent != null) + { + var u = await this.userProfileService.GetByIdAsync(unpublishEvent.CreateUserId); + card.UnpublishedByAdmin = this.userService.IsAdminUser(u.Id) && u.Id != userId; + card.UnpublishedBy = $"{u.UserName}"; + card.UnpublishedDate = unpublishEvent.CreateDate.DateTime; + } + } + + // 'Flagged' details + var flag = resourceVersion.ResourceVersionFlag.FirstOrDefault(f => f.IsActive); + if (flag != null) + { + card.IsFlagged = true; + var u = await this.userProfileService.GetByIdAsync(flag.CreateUserId); + card.FlaggedByAdmin = this.userService.IsAdminUser(u.Id); + card.FlaggedBy = $"{u.FirstName} {u.LastName}"; + card.FlaggedDate = flag.CreateDate.DateTime; + } + } + /// + /// The populate resource version view model. + /// + /// The resourceVersionViewModel. + /// The resourceVersion. + /// The . + private async Task PopulateResourceVersionViewModel(ResourceVersionViewModel resourceVersionViewModel, ResourceVersion resourceVersion) + { + if (resourceVersion != null) + { + resourceVersionViewModel.ResourceId = resourceVersion.ResourceId; + resourceVersionViewModel.ResourceVersionId = resourceVersion.Id; + resourceVersionViewModel.VersionStatusEnum = resourceVersion.VersionStatusEnum; + resourceVersionViewModel.Title = resourceVersion.Title; + resourceVersionViewModel.Description = resourceVersion.Description; + resourceVersionViewModel.SensitiveContent = resourceVersion.SensitiveContent; + resourceVersionViewModel.ResourceTypeEnum = resourceVersion.Resource.ResourceTypeEnum; + resourceVersionViewModel.MajorVersion = resourceVersion.MajorVersion; + resourceVersionViewModel.MinorVersion = resourceVersion.MinorVersion; + + if (resourceVersion.ReviewDate.HasValue) + { + resourceVersionViewModel.NextReviewDate = resourceVersion.ReviewDate.Value.DateTime; + } + + resourceVersionViewModel.AdditionalInformation = resourceVersion.AdditionalInformation; + resourceVersionViewModel.ResourceFree = !resourceVersion.HasCost; + resourceVersionViewModel.Cost = resourceVersion.HasCost ? (double)resourceVersion.Cost : 0.00d; + + resourceVersionViewModel.Keywords = new List(); + foreach (var k in resourceVersion.ResourceVersionKeyword) + { + resourceVersionViewModel.Keywords.Add(k.Keyword); + } + + resourceVersionViewModel.Authors = new List(); + foreach (var a in resourceVersion.ResourceVersionAuthor) + { + string displayText = string.Empty; + + if (!string.IsNullOrEmpty(a.AuthorName) && !string.IsNullOrEmpty(a.Organisation)) + { + displayText = string.Join(", ", a.AuthorName, a.Organisation); + } + + if (!string.IsNullOrEmpty(a.AuthorName) && string.IsNullOrEmpty(a.Organisation)) + { + displayText = a.AuthorName; + } + + if (string.IsNullOrEmpty(a.AuthorName) && !string.IsNullOrEmpty(a.Organisation)) + { + displayText = a.Organisation; + } + + if (!string.IsNullOrEmpty(a.Role)) + { + displayText += ", " + a.Role; + } + + resourceVersionViewModel.Authors.Add(displayText); + } + + resourceVersionViewModel.AuthoredDate = resourceVersion.CreateDate.DateTime; + + if (resourceVersion.VersionStatusEnum == VersionStatusEnum.Published + || resourceVersion.VersionStatusEnum == VersionStatusEnum.Unpublished) + { + if (resourceVersion.Publication != null) + { + var u = await this.userProfileService.GetByIdAsync(resourceVersion.Publication.CreateUserId); + resourceVersionViewModel.PublishedBy = $"{u.FirstName} {u.LastName}"; + resourceVersionViewModel.PublishedDate = resourceVersion.Publication.CreateDate.DateTime; + resourceVersionViewModel.PublishedNotes = resourceVersion.Publication.Notes; + } + } + + if (resourceVersion.ResourceLicence != null) + { + resourceVersionViewModel.LicenseName = resourceVersion.ResourceLicence.Title; + } + + resourceVersionViewModel.CreateUserId = resourceVersion.CreateUserId; + resourceVersionViewModel.CreateUser = resourceVersion.CreateUser.UserName; + resourceVersionViewModel.CreateDate = resourceVersion.CreateDate; + } + } + } + } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserPasswordResetRequestsService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserPasswordResetRequestsService.cs new file mode 100644 index 000000000..086e1aef7 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserPasswordResetRequestsService.cs @@ -0,0 +1,80 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Logging; + + /// + /// The user service. + /// + public class UserPasswordResetRequestsService : IUserPasswordResetRequestsService + { + /// + /// The user password reset requests repository. + /// + private readonly IUserPasswordResetRequestsRepository userPasswordResetRequestsRepository; + + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// The cache. + /// + private readonly ICachingService cachingService; + + /// + /// The logger. + /// + private readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// The user password reset requests repository. + /// The mapper. + /// The caching service. + /// The logger. + /// The userDetailsRepository. + public UserPasswordResetRequestsService( + IUserPasswordResetRequestsRepository userPasswordResetRequestsRepository, + IMapper mapper, + ICachingService cachingService, + ILogger logger, + IUserProfileRepository userDetailsRepository) + { + this.userPasswordResetRequestsRepository = userPasswordResetRequestsRepository; + this.mapper = mapper; + this.cachingService = cachingService; + this.logger = logger; + } + + /// + /// The get by username async. + /// + /// The user name. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// The . + public async Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + var result = await userPasswordResetRequestsRepository.CanRequestPasswordResetAsync(emailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + + return result; + } + + /// + /// CreateUserPasswordRequest. + /// + /// the emailAddress. + /// The . + public async Task CreateUserPasswordRequest(string emailAddress) + { + var result = await userPasswordResetRequestsRepository.CreatePasswordRequests(emailAddress); + return result; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProfileService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProfileService.cs index 11ab989f1..dbb761cea 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProfileService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserProfileService.cs @@ -61,8 +61,8 @@ public async Task CreateUserProfileAsync(int userId if (retVal.IsValid) { - retVal.CreatedId = await userProfileRepository.CreateAsync(userId, userProfile); - } + retVal.CreatedId = await userProfileRepository.CreateAsync(userId, userProfile); + } return retVal; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index 86406d0e5..4b3bba6cc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -62,6 +62,7 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHHandler.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHHandler.cs new file mode 100644 index 000000000..bafefa31f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHHandler.cs @@ -0,0 +1,87 @@ +namespace LearningHub.NHS.OpenAPI.Authentication +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Api.Authentication; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Options; + + /// + /// Provide Authentication policy for Auth Service. + /// + public class AuthorizeOrCallFromLHHandler : AuthorizationHandler + { + /// + /// The context accessor. + /// + private readonly IHttpContextAccessor contextAccessor; + + /// + /// The settings. + /// + private readonly IOptions settings; + + /// + /// Initializes a new instance of the class. + /// Provide Authentication policy for Auth Service. + /// + /// The context Accessor. + /// The settings. + public AuthorizeOrCallFromLHHandler(IHttpContextAccessor contextAccessor, IOptions settings) + { + this.contextAccessor = contextAccessor; + this.settings = settings; + } + + /// + /// Handle Authentication policy Requirement. + /// + /// The context. + /// The requirement. + /// A representing the asynchronous operation. + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeOrCallFromLHRequirement requirement) + { + bool callFromAuthService = false; + bool callFromLearningHubClient = false; + bool callFromLHContentServerClient = false; + bool callFromReportApiClient = false; + + if (!context.User.Identity.IsAuthenticated) + { + var headers = contextAccessor.HttpContext.Request.Headers; + + // Note: headers.ContainsKey and headers.TryGetValue are case-insensitive. + if (headers.ContainsKey("Client-Identity-Key")) + { + Microsoft.Extensions.Primitives.StringValues clientKeyValues; + if (headers.TryGetValue("Client-Identity-Key", out clientKeyValues)) + { + string clientKey = clientKeyValues.First().ToUpperInvariant(); + + callFromAuthService = clientKey + == settings.Value.AuthClientIdentityKey.ToUpperInvariant(); + + callFromLearningHubClient = clientKey + == settings.Value.LHClientIdentityKey.ToUpperInvariant(); + + callFromLHContentServerClient = clientKey + == settings.Value.ContentServerClientIdentityKey.ToUpperInvariant(); + + callFromReportApiClient = clientKey + == settings.Value.ReportApiClientIdentityKey.ToUpperInvariant(); + } + } + } + + if (!callFromAuthService && !callFromLearningHubClient && !callFromLHContentServerClient && !callFromReportApiClient && !context.User.Identity.IsAuthenticated) + { + return Task.CompletedTask; + } + + context.Succeed(requirement); + return Task.CompletedTask; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHRequirement.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHRequirement.cs new file mode 100644 index 000000000..b84ff4588 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/AuthorizeOrCallFromLHRequirement.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.Api.Authentication +{ + using Microsoft.AspNetCore.Authorization; + + /// + /// Provide Authentication policy for Auth Service. + /// + public class AuthorizeOrCallFromLHRequirement : IAuthorizationRequirement + { + /// + /// Initializes a new instance of the class. + /// Provide Authentication policy for Auth Service. + /// + public AuthorizeOrCallFromLHRequirement() + { + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index b62847a6d..9e8be7d55 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -667,6 +667,42 @@ public ActionResult GetMyContributionsTotalsAsync(int catalogueId) return this.Ok(this.resourceService.GetMyContributionTotals(catalogueId, this.CurrentUserId.GetValueOrDefault())); } + /// + /// Returns Resource Cards for "My Contributions". + /// + /// The resourceContributionsRequestViewModel. + /// The . + [HttpPost] + [Route("GetContributions")] + public ActionResult GetMyContributionsAsync(ResourceContributionsRequestViewModel resourceContributionsRequestViewModel) + { + return this.Ok(this.resourceService.GetContributions(this.CurrentUserId.GetValueOrDefault(), resourceContributionsRequestViewModel, this.HttpContext.User.IsInRole("ReadOnly"))); + } + + + /// + /// Returns Resource Cards. + /// + /// The . + [HttpGet] + [Route("GetMyResourceViewModel")] + public async Task GetMyResourceViewModelAsync() + { + return this.Ok(await this.resourceService.GetMyResourceViewModelAsync(this.CurrentUserId.GetValueOrDefault())); + } + + /// + /// Returns Extended Card details for the supplied Id (resourceVersionId). + /// + /// The resourceVersionId. + /// The . + [HttpGet] + [Route("ResourceCardExtendedViewModel/{resourceVersionId}")] + public async Task GetResourceCardExtendedViewModelAsync(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetResourceCardExtendedViewModelAsync(resourceVersionId, this.CurrentUserId.GetValueOrDefault())); + } + /// /// Returns if the user has published resources. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index e83fe498a..a7f1741cb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -34,6 +34,7 @@ public class UserController : OpenApiControllerBase private readonly ICacheService cacheService; private readonly LearningHubConfig learningHubConfig; private readonly IUserNotificationService userNotificationService; + private readonly IUserPasswordResetRequestsService userPasswordResetRequestsService; private readonly INavigationPermissionService permissionService; /// @@ -57,6 +58,7 @@ public UserController( IUserProfileService userProfileService, ISecurityService securityService, IUserNotificationService userNotificationService, + IUserPasswordResetRequestsService userPasswordResetRequestsService, INavigationPermissionService permissionService, ICacheService cacheService, IOptions learningHubConfig) @@ -65,6 +67,7 @@ public UserController( this.securityService = securityService; this.userService = userService; this.userNotificationService = userNotificationService; + this.userPasswordResetRequestsService = userPasswordResetRequestsService; this.permissionService = permissionService; this.cacheService = cacheService; this.learningHubConfig = learningHubConfig.Value; @@ -276,6 +279,39 @@ public async Task ValidateEmailChangeTokenAsync(string token, str return this.Ok(result); } + /// + /// Check user can request password reset. + /// + /// emailAddress. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// + /// The . + /// + [HttpGet] + [AllowAnonymous] + [Route("CanRequestPasswordReset/{emailAddress}/{passwordRequestLimitingPeriod}/{passwordRequestLimit}")] + public async Task CanRequestPasswordReset(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + var result = await this.userPasswordResetRequestsService.CanRequestPasswordReset(emailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + if (result) + { + await this.userPasswordResetRequestsService.CreateUserPasswordRequest(emailAddress); + } + + return result; + } + + /// + /// The GetActiveContent. + /// + /// The active content. + [HttpGet("GetActiveContent")] + public async Task GetActiveContent() + { + return this.Ok(await this.userService.GetActiveContentAsync(this.CurrentUserId.GetValueOrDefault())); + } + /// /// GetLHUserNavigation. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index 533227ccd..29db6598f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -31,6 +31,7 @@ namespace LearningHub.NHS.OpenAPI using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.Authorization; using LearningHub.NHS.OpenAPI.Authentication; + using LearningHub.Nhs.Api.Authentication; /// /// The Startup class. @@ -75,7 +76,8 @@ public void ConfigureServices(IServiceCollection services) }); services.AddCustomMiddleware(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddRepositories(this.Configuration); services.AddServices(); @@ -155,6 +157,10 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthorization(options => { + options.AddPolicy( + "AuthorizeOrCallFromLH", + policy => policy.Requirements.Add(new AuthorizeOrCallFromLHRequirement())); + options.AddPolicy( "ReadWrite", policy => policy.Requirements.Add(new ReadWriteRequirement())); From a6e7be656785b59e361c1371d091db86b9015bed Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 12:36:53 +0100 Subject: [PATCH 24/83] . --- LearningHub.Nhs.WebUI/Services/DashboardService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 24dabdfef..607d4f2be 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -48,7 +48,7 @@ public async Task GetMyAccessLearningsAsyn var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"dashboard/myaccesslearning/{dashboardType}/{pageNumber}"; - var response = await client.GetAsync(request).ConfigureAwait(true); + var response = await client.GetAsync(request).ConfigureAwait(false); if (response.IsSuccessStatusCode) { From 7a482a3822baf739d99edc1fee27d4f6a84a8f55 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 12:39:23 +0100 Subject: [PATCH 25/83] api key appsettings variable update --- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 804dc13f5..13dc2665a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -77,6 +77,10 @@ "UseRedisCache": true, "ResourcePublishQueueRouteName": "", "HierarchyEditPublishQueueName": "", + "AuthClientIdentityKey": "", + "LHClientIdentityKey": "", + "ReportApiClientIdentityKey": "", + "ContentServerClientIdentityKey": "", "Notifications": { "PublishResourceTimeToProcessInSec": 30, "ResourcePublishedTitle": "Published: ", From ff61a9e1e126086831e31a2d19016c18c84f521d Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 13:06:34 +0100 Subject: [PATCH 26/83] Route typo fix --- .../LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 3e53a243f..6f0ed2207 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -12,7 +12,7 @@ /// /// Catalogue controller. /// - [Route("Catalogues")] + [Route("Catalogue")] [Authorize] public class CatalogueController : OpenApiControllerBase { From 19f0a1be2fd0b5f7f3bb86940efb6f36ec5c2222 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 13:34:12 +0100 Subject: [PATCH 27/83] endpoint update --- .../ICatalogueNodeVersionRepository.cs | 8 +++ .../CatalogueNodeVersionRepository.cs | 16 +++++ .../Services/ICatalogueService.cs | 8 +++ .../Services/CatalogueService.cs | 61 +++++++++++++++++++ .../Controllers/CatalogueController.cs | 13 ++++ .../Controllers/UserController.cs | 12 ++++ 6 files changed, 118 insertions(+) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs index e1137c520..846b83bdc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs @@ -137,5 +137,13 @@ public interface ICatalogueNodeVersionRepository : IGenericRepositoryThe userId. /// The catalogues. Task> GetAllCataloguesAsync(int pageSize, string filterChar, int userId); + + /// + /// Gets catalogues based on filter character. + /// + /// The filterChar. + /// The userId. + /// The catalogues. + Task> GetAllCataloguesAsync(string filterChar, int userId); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs index ee2dd0f50..f533a79be 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -389,5 +389,21 @@ public async Task> GetAllCataloguesAsync(int pageSiz .AsNoTracking().ToListAsync(); return result; } + + /// + /// Gets catalogues based on filter character. + /// + /// The filterChar. + /// The userId. + /// resources. + public async Task> GetAllCataloguesAsync(string filterChar, int userId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@filterChar", SqlDbType.NVarChar, 10) { Value = filterChar.Trim() }; + + var result = await this.DbContext.AllCatalogueViewModel.FromSqlRaw("[hierarchy].[GetCatalogues] @userId, @filterChar", param0, param1) + .AsNoTracking().ToListAsync(); + return result; + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs index ee252f473..099deceed 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs @@ -223,5 +223,13 @@ public interface ICatalogueService /// The validation result. Task AcceptAccessAsync(int userId, int accessRequestId); + /// + /// GetAllCataloguesAsync. + /// + /// filterChar. + /// userId. + /// The allcatalogue result based on letters. + Task GetAllCataloguesAsync(string filterChar, int userId); + } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index c83474c1f..673f3eb7d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -111,6 +111,67 @@ public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepositor return new Models.ViewModels.BulkCatalogueViewModel(catalogueViewModels); } + /// + /// GetAllCataloguesAsync. + /// + /// The filterChar. + /// The userId. + /// A representing the result of the asynchronous operation. + public async Task GetAllCataloguesAsync(string filterChar, int userId) + { + var catalogueAlphaCount = this.catalogueNodeVersionRepository.GetAllCataloguesAlphaCount(userId); + var filterCharMod = filterChar.Trim() == "0-9" ? "[0-9]" : filterChar; + var count = catalogueAlphaCount.FirstOrDefault(ca => ca.Alphabet == filterChar.ToUpper()).Count; + string prevChar = null, nextChar = null, curChar = null; + var filterCharIndex = catalogueAlphaCount.FindIndex(ca => ca.Alphabet == filterChar.ToUpper()); + + // check count and assign prev and next letter + if (count != 0) + { + for (int i = 0; i < catalogueAlphaCount.Count; i++) + { + if (i == filterCharIndex && i == 0) + { + prevChar = null; + } + + if (i == filterCharIndex && i == catalogueAlphaCount.Count - 1) + { + nextChar = null; + } + + if (catalogueAlphaCount[i].Count > 0 && i < filterCharIndex) + { + curChar = catalogueAlphaCount[i].Alphabet; + prevChar = curChar; + } + + if (catalogueAlphaCount[i].Count > 0 && i > filterCharIndex) + { + curChar = catalogueAlphaCount[i].Alphabet; + nextChar = curChar; + break; + } + } + } + + var catalogues = await this.catalogueNodeVersionRepository.GetAllCataloguesAsync(filterCharMod, userId); + foreach (var catalogue in catalogues) + { + catalogue.Providers = await this.providerService.GetByCatalogueVersionIdAsync(catalogue.NodeVersionId); + } + + var response = new AllCatalogueResponseViewModel + { + CataloguesCount = catalogueAlphaCount, + Catalogues = catalogues, + FilterChar = filterChar.ToUpper(), + PrevChar = prevChar, + NextChar = nextChar, + }; + return response; + } + /// /// The Get Basic Catalogue. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 6f0ed2207..9372096dd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -37,6 +37,19 @@ public CatalogueController(ICatalogueService catalogueService) return await this.catalogueService.GetAllCatalogues(); } + /// + /// Gets AllCatalogues. + /// + /// The filterChar. + /// IActionResult. + [HttpGet] + [Route("allcatalogues/{filterChar}")] + public async Task GetAllCataloguesAsync(string filterChar = null) + { + var response = await this.catalogueService.GetAllCataloguesAsync(filterChar, this.CurrentUserId.GetValueOrDefault()); + return this.Ok(response); + } + /// /// The GetCatalogue. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index a7f1741cb..bfcfe450e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -302,6 +302,18 @@ public async Task CanRequestPasswordReset(string emailAddress, int passwor return result; } + /// + /// Get specific User Profile by Id. + /// + /// The id. + /// The . + [HttpGet] + [Route("GetUserProfile/{id}")] + public async Task> GetUserProfileAsync(int id) + { + return this.Ok(await this.userProfileService.GetByIdAsync(id)); + } + /// /// The GetActiveContent. /// From b3e9c37ead3d618df942cb4eaa419705c89c2440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:23:36 +0000 Subject: [PATCH 28/83] Bump AngleSharp and 49 others Bumps AngleSharp from 0.16.1 to 1.3.0 Bumps AspNetCore.Authentication.ApiKey from 8.0.1 to 9.0.0 Bumps AutoMapper to 14.0.0 Bumps AutoMapper.Extensions.Microsoft.DependencyInjection to 12.0.1 Bumps Azure.Messaging.ServiceBus from 7.18.3 to 7.20.1 Bumps Azure.Storage.Blobs from 12.23.0 to 12.24.1 Bumps Azure.Storage.Files.Shares from 12.8.0 to 12.11.0 Bumps Azure.Storage.Queues from 12.11.0 to 12.22.0 Bumps coverlet.collector from 1.3.0 to 6.0.4 Bumps EntityFrameworkCore.Testing.Moq from 5.0.0 to 9.0.1 Bumps FluentValidation to 12.0.0 Bumps FluentValidation.AspNetCore to 11.3.1 Bumps HtmlAgilityPack from 1.11.72 to 1.12.1 Bumps HtmlSanitizer from 6.0.453 to 9.0.886 Bumps Microsoft.ApplicationInsights.AspNetCore from 2.21.0 to 2.23.0 Bumps Microsoft.ApplicationInsights.EventCounterCollector from 2.21.0 to 2.23.0 Bumps Microsoft.ApplicationInsights.NLogTarget from 2.22.0 to 2.23.0 Bumps Microsoft.AspNetCore.Authentication.JwtBearer from 6.0.36 to 8.0.17 Bumps Microsoft.AspNetCore.Authentication.OpenIdConnect from 6.0.36 to 8.0.17 Bumps Microsoft.AspNetCore.Mvc.NewtonsoftJson from 6.0.36 to 8.0.17 Bumps Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation from 6.0.36 to 7.0.0 Bumps Microsoft.AspNetCore.Mvc.Testing from 3.1.13 to 7.0.0 Bumps Microsoft.EntityFrameworkCore to 9.0.0, 9.0.6 Bumps Microsoft.EntityFrameworkCore.Relational to 9.0.0, 9.0.6 Bumps Microsoft.EntityFrameworkCore.SqlServer to 9.0.6 Bumps Microsoft.Extensions.Configuration.Abstractions to 9.0.6 Bumps Microsoft.Extensions.Configuration.Json from 9.0.1 to 9.0.6 Bumps Microsoft.Extensions.DependencyInjection.Abstractions to 9.0.6 Bumps Microsoft.Extensions.Logging.Abstractions to 9.0.6 Bumps Microsoft.Extensions.Options to 8.0.0, 9.0.0, 9.0.6 Bumps Microsoft.FeatureManagement from 4.0.0 to 4.1.0 Bumps Microsoft.IdentityModel.Protocols.OpenIdConnect from 8.3.1 to 8.12.0 Bumps Microsoft.IdentityModel.Tokens from 8.3.1 to 8.12.0 Bumps Microsoft.NET.Test.Sdk from 17.1.0 to 17.14.1 Bumps Microsoft.TypeScript.MSBuild from 5.7.1 to 5.8.3 Bumps Microsoft.VisualStudio.Web.CodeGeneration.Design from 6.0.18 to 7.0.0 Bumps MK.IO from 1.6.0 to 2.1.3 Bumps NHSUKViewComponents.Web from 1.0.28 to 1.0.29 Bumps NLog.Schema from 5.3.4 to 5.5.0 Bumps NLog.Web.AspNetCore from 4.15.0 to 5.5.0 Bumps Selenium.Axe from 4.0.19 to 4.0.21 Bumps Selenium.Support from 4.19.0 to 4.33.0 Bumps Selenium.WebDriver from 4.19.0 to 4.33.0 Bumps Serilog.AspNetCore from 3.2.0 to 9.0.0 Bumps Swashbuckle.AspNetCore from 7.2.0 to 9.0.1 Bumps System.Configuration.ConfigurationManager to 9.0.6 Bumps System.Drawing.Common from 9.0.1 to 9.0.6 Bumps System.IdentityModel.Tokens.Jwt to 8.12.0 Bumps System.Security.Cryptography.Pkcs from 9.0.1 to 9.0.6 Bumps tusdotnet from 2.8.0 to 2.10.0 --- updated-dependencies: - dependency-name: AngleSharp dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: HtmlSanitizer dependency-version: 9.0.886 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AspNetCore.Authentication.ApiKey dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper dependency-version: 14.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Options dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper dependency-version: 14.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Options dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper dependency-version: 14.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper.Extensions.Microsoft.DependencyInjection dependency-version: 12.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper.Extensions.Microsoft.DependencyInjection dependency-version: 12.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper.Extensions.Microsoft.DependencyInjection dependency-version: 12.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper.Extensions.Microsoft.DependencyInjection dependency-version: 12.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: AutoMapper.Extensions.Microsoft.DependencyInjection dependency-version: 12.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Azure.Messaging.ServiceBus dependency-version: 7.20.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Azure.Storage.Blobs dependency-version: 12.24.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Azure.Storage.Files.Shares dependency-version: 12.11.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Azure.Storage.Queues dependency-version: 12.22.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: coverlet.collector dependency-version: 6.0.4 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: EntityFrameworkCore.Testing.Moq dependency-version: 9.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.Relational dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Options dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: FluentValidation dependency-version: 12.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: FluentValidation dependency-version: 12.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: FluentValidation.AspNetCore dependency-version: 11.3.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: FluentValidation.AspNetCore dependency-version: 11.3.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: FluentValidation.AspNetCore dependency-version: 11.3.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: HtmlAgilityPack dependency-version: 1.12.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.ApplicationInsights.AspNetCore dependency-version: 2.23.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.ApplicationInsights.EventCounterCollector dependency-version: 2.23.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.ApplicationInsights.NLogTarget dependency-version: 2.23.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer dependency-version: 8.0.17 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Authentication.OpenIdConnect dependency-version: 8.0.17 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Mvc.NewtonsoftJson dependency-version: 8.0.17 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.DependencyInjection.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Options dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.Relational dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Configuration.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.Relational dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.SqlServer dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Configuration.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.Relational dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.EntityFrameworkCore.SqlServer dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Configuration.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Configuration.Json dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.Extensions.DependencyInjection.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.Extensions.Options dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.FeatureManagement dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.IdentityModel.Protocols.OpenIdConnect dependency-version: 8.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.IdentityModel.Tokens dependency-version: 8.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: System.IdentityModel.Tokens.Jwt dependency-version: 8.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 17.14.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.TypeScript.MSBuild dependency-version: 5.8.3 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.VisualStudio.Web.CodeGeneration.Design dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: MK.IO dependency-version: 2.1.3 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: NHSUKViewComponents.Web dependency-version: 1.0.29 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: NLog.Schema dependency-version: 5.5.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: NLog.Web.AspNetCore dependency-version: 5.5.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Selenium.Axe dependency-version: 4.0.21 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Selenium.Support dependency-version: 4.33.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Selenium.WebDriver dependency-version: 4.33.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Serilog.AspNetCore dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Swashbuckle.AspNetCore dependency-version: 9.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: System.Configuration.ConfigurationManager dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: System.Configuration.ConfigurationManager dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: System.Configuration.ConfigurationManager dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: System.Drawing.Common dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: System.IdentityModel.Tokens.Jwt dependency-version: 8.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: System.IdentityModel.Tokens.Jwt dependency-version: 8.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: System.Security.Cryptography.Pkcs dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: tusdotnet dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 96 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8f1d8febf..1e0867add 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,77 +15,77 @@ - + - - - - - - + + + + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - - - - + + + + + + - + - + - + - - - - + + + + From caac5f13d8a2a09f26cfff91fe68dae223574d95 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 17 Jun 2025 15:42:33 +0100 Subject: [PATCH 29/83] service dependency issue --- .../ServiceCollectionExtension.cs | 9 +++++++++ .../Services/ICatalogueService.cs | 7 +++++++ .../Services/CatalogueService.cs | 20 +++++++++++++++++++ .../Controllers/CatalogueController.cs | 13 ++++++++++++ 4 files changed, 49 insertions(+) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs index 81867365f..1039a3673 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs @@ -86,6 +86,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddSingleton(configuration); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -132,11 +133,19 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); + services.AddHttpClient() + .ConfigurePrimaryHttpMessageHandler( + () => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }); } else { services.AddHttpClient(); services.AddHttpClient(); + services.AddHttpClient(); } services.AddTransient(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs index 099deceed..f763808ef 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs @@ -26,6 +26,13 @@ public interface ICatalogueService /// BulkCatalogueViewModel. public Task GetAllCatalogues(); + /// + /// The GetCatalogues. + /// + /// The searchTerm. + /// The catalogues. + List GetCatalogues(string searchTerm); + /// /// The GetCatalogue. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 673f3eb7d..f75c0616e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -11,6 +11,7 @@ using LearningHub.Nhs.Models.Email.Models; using LearningHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.Models.Entities.Hierarchy; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Resource; @@ -183,6 +184,25 @@ public CatalogueBasicViewModel GetBasicCatalogue(int catalogueNodeId) return this.mapper.Map(catalogue); } + /// + /// The GetCatalogues. + /// + /// The searchTerm. + /// The catalogues. + public List GetCatalogues(string searchTerm) + { + IQueryable catalogueVersions = this.catalogueNodeVersionRepository.GetAll() + .Include(x => x.Keywords) + .Include(x => x.CatalogueNodeVersionProvider).Where(x => !x.Deleted) + .Include(x => x.NodeVersion) + .ThenInclude(x => x.Node); + if (!string.IsNullOrEmpty(searchTerm)) + { + catalogueVersions = catalogueVersions.Where(x => x.Name.ToLower().Contains(searchTerm.ToLower())); + } + + return this.mapper.ProjectTo(catalogueVersions).ToList(); + } /// /// The GetCatalogue. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 9372096dd..3d6e330ff 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -50,6 +50,19 @@ public async Task GetAllCataloguesAsync(string filterChar = null) return this.Ok(response); } + /// + /// The GetCatalogue. + /// + /// The searchTerm. + /// The catalogues. + [HttpGet] + [Route("Catalogues")] + public IActionResult GetCatalogues(string searchTerm) + { + var catalogues = this.catalogueService.GetCatalogues(searchTerm); + return this.Ok(catalogues); + } + /// /// The GetCatalogue. /// From 949ec30db58a00f5d70f84934e06ab4ceee33ca6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 15:04:29 +0000 Subject: [PATCH 30/83] Bump AngleSharp and 4 others Bumps AngleSharp from 0.16.1 to 1.3.0 Bumps HtmlSanitizer from 6.0.453 to 9.0.886 Bumps Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation from 7.0.0 to 8.0.17 Bumps Microsoft.AspNetCore.Mvc.Testing from 7.0.0 to 8.0.17 Bumps Microsoft.VisualStudio.Web.CodeGeneration.Design from 7.0.0 to 9.0.0 --- updated-dependencies: - dependency-name: AngleSharp dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: HtmlSanitizer dependency-version: 9.0.886 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation dependency-version: 8.0.17 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-version: 8.0.17 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.VisualStudio.Web.CodeGeneration.Design dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1e0867add..08005beae 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -43,8 +43,8 @@ - - + + @@ -64,7 +64,7 @@ - + From 07ff7c4794ffbc1f08b9f73040adae6f72507d2d Mon Sep 17 00:00:00 2001 From: Binon Date: Wed, 18 Jun 2025 08:43:14 +0100 Subject: [PATCH 31/83] Revering web.config --- LearningHub.Nhs.WebUI/web.config | 41 ++++++++++++--------------- WebAPI/LearningHub.Nhs.API/web.config | 38 +++++++++++-------------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/LearningHub.Nhs.WebUI/web.config b/LearningHub.Nhs.WebUI/web.config index 541f29920..837247997 100644 --- a/LearningHub.Nhs.WebUI/web.config +++ b/LearningHub.Nhs.WebUI/web.config @@ -1,26 +1,21 @@  - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/WebAPI/LearningHub.Nhs.API/web.config b/WebAPI/LearningHub.Nhs.API/web.config index 6483ee683..8771970fe 100644 --- a/WebAPI/LearningHub.Nhs.API/web.config +++ b/WebAPI/LearningHub.Nhs.API/web.config @@ -1,25 +1,21 @@  - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file From 5b9eeb2f832e6eb0ae43a4177a97f37a1207a908 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 18 Jun 2025 09:13:24 +0100 Subject: [PATCH 32/83] TD-5501 Route fix for GetallbyParent Bookmark dual endpoint --- .../Controllers/BookmarkController.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs index c0bb7d4a2..867f295f6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/BookmarkController.cs @@ -24,18 +24,6 @@ public BookmarkController(IBookmarkService bookmarkService) this.bookmarkService = bookmarkService; } - /// - /// - /// Gets all bookmarks by parent. - /// - /// Bookmarks. - [HttpGet] - [Route("GetAllByParent")] - public async Task> GetAllByParent() - { - return await this.bookmarkService.GetAllByParent(this.TokenWithoutBearer); - } - /// /// The GetAllByParent. /// @@ -43,14 +31,21 @@ public async Task> GetAllByParent() /// The all. /// The . [HttpGet] + [Route("GetAllByParent")] [Route("GetAllByParent/{parentId?}")] public async Task GetAllByParent(int? parentId, bool? all = false) { - var bookmarks = await this.bookmarkService.GetAllByParent(this.CurrentUserId.GetValueOrDefault(), parentId, all); - return this.Ok(bookmarks); + if (this.CurrentUserId.GetValueOrDefault() != null) + { + var bookmarks = await this.bookmarkService.GetAllByParent(this.CurrentUserId.GetValueOrDefault(), parentId, all); + return this.Ok(bookmarks); + } + else + { + return this.Ok(await this.bookmarkService.GetAllByParent(this.TokenWithoutBearer)); + } } - /// /// The Create. /// From 868878f685b0b02adfe7d26a55ff7f403a0a9a46 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:09:44 +0100 Subject: [PATCH 33/83] Update Directory.Packages.props-System.IdentityModel.Tokens.Jwt --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 08005beae..cce46b124 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -83,11 +83,11 @@ - + - \ No newline at end of file + From bf84f42f9c58476d4948f41a28dbc54ae990d1c4 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:24:45 +0100 Subject: [PATCH 34/83] Update Directory.Packages.props-Automapper version --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cce46b124..516cfe4ef 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,8 +18,8 @@ - - + + From 63c876a312dba6f768b15deb6a791a4c83cbb93c Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:49:04 +0100 Subject: [PATCH 35/83] Update Directory.Packages.props --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 516cfe4ef..8bde0b032 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -83,7 +83,7 @@ - + From 783909649e675d177073bff039f3a9bd7e816350 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 18 Jun 2025 11:42:43 +0100 Subject: [PATCH 36/83] For testing purpose commented the cache call. --- LearningHub.Nhs.WebUI/Services/UserGroupService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 6a4f62966..03581cb38 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -47,7 +47,8 @@ public UserGroupService( public async Task> GetRoleUserGroupDetailAsync() { var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; - return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); + ////return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); + return await this.FetchRoleUserGroupDetailAsync(); } /// From 0fd95477794c4976cbb5bd8a16236b6c803c8bda Mon Sep 17 00:00:00 2001 From: binon Date: Wed, 18 Jun 2025 14:08:14 +0100 Subject: [PATCH 37/83] Delete nuget.config --- nuget.config | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 nuget.config diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 33abf6d35..000000000 --- a/nuget.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - From 2dc2f527e282a7cbf384ac1ae615b60e19385f9e Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 18 Jun 2025 14:43:48 +0100 Subject: [PATCH 38/83] TD-5496 --- LearningHub.Nhs.WebUI/Controllers/HomeController.cs | 4 ++++ .../LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj | 3 ++- OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs | 7 +++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 7f40afe15..e5936fcdd 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -274,6 +274,10 @@ public async Task LoadPage(string dashBoardTray = "my-learning", Catalogues = new Nhs.Models.Dashboard.DashboardCatalogueResponseViewModel { Type = catalogueDashBoard }, }; + var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; + this.ViewBag.EnableMoodle = enableMoodle; + this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0; + bool isAjax = this.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; if (isAjax) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 83639e186..e8ddf4415 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -1,4 +1,4 @@ - + enable @@ -21,6 +21,7 @@ + all diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index 29db6598f..e5f9c180e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -90,12 +90,11 @@ public void ConfigureServices(IServiceCollection services) { options.Filters.Add(new HttpResponseExceptionFilter()); options.Filters.Add(new AuthorizeFilter()); - }) - .AddJsonOptions(options => - { - options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; }); + services.AddMvc() + .AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); + services.AddSwaggerGen( c => { From e69bf4dbf924871167d7222879b56de17013d63d Mon Sep 17 00:00:00 2001 From: Arunima George Date: Wed, 18 Jun 2025 16:40:16 +0100 Subject: [PATCH 39/83] TD-5498: Search issue fixed --- .../Services/SearchService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index 99f0e2830..82c9a52ca 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -106,7 +106,7 @@ public async Task GetSearchResultAsync(SearchRequestModel sea var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "?offset={1}&hits={2}&q={3}&token={4}", + this.findwiseConfig.SearchEndpointPath + "{0}?offset={1}&hits={2}&q={3}&token={4}", this.findwiseConfig.CollectionIds.Resource, offset, pageSize, @@ -183,7 +183,7 @@ public async Task GetCatalogueSearchResultAsync(Cata var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "?offset={1}&hits={2}&q={3}&token={4}", + this.findwiseConfig.SearchEndpointPath + "{0}?offset={1}&hits={2}&q={3}&token={4}", this.findwiseConfig.CollectionIds.Catalogue, offset, catalogSearchRequestModel.PageSize, From 1fb60731be714a60e35938b89e70929643708f8f Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 19 Jun 2025 09:07:04 +0100 Subject: [PATCH 40/83] TD-5499: Replace Web API layer with Open API -My Contributions-Contribute/View/edit/Publish/Unpublish- SIT issues --- .../Configuration/AzureConfig.cs | 1 + .../Services/IResourceService.cs | 15 +++ .../Services/ResourceService.cs | 113 ++++++++++-------- .../Controllers/ResourceController.cs | 30 ++++- .../SwaggerDefinitions/v1.3.0.json | 50 ++++++++ 5 files changed, 157 insertions(+), 52 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs index 9b3ad76d1..55832bb7b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs @@ -9,6 +9,7 @@ public class AzureConfig /// Gets or sets the azure blob settings. /// public AzureBlobSettings AzureBlobSettings { get; set; } = null!; + /// /// Gets or sets the azure storage queue. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index d71c89701..2a83eed80 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -225,6 +225,13 @@ Task GetAssessmentProgress( /// If the user has published resources. Task HasPublishedResourcesAsync(int userId); + /// + /// The get resource version by id async. + /// + /// The resourceVersionId. + /// The . + Task GetResourceVersionByIdAsync(int resourceVersionId); + /// /// The get resource by activityStatusIds async. /// @@ -300,6 +307,14 @@ Task GetAssessmentProgress( /// The . Task GetCaseDetailsByIdAsync(int resourceVersionId); + /// + /// The GetExternalContentDetails. + /// + /// The resourceVersionId. + /// userId. + /// The . + Task GetExternalContentDetails(int resourceVersionId, int userId); + /// /// The get case resource version async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index 7102145e5..07a55e537 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -521,6 +521,22 @@ public async Task GetCaseDetailsByIdAsync(int resourceVersionId) return caseViewModel; } + /// + /// The GetExternalContentDetails. + /// + /// The resourceVersionId. + /// userId. + /// The . + public async Task GetExternalContentDetails(int resourceVersionId, int userId) + { + var viewModel = await this.resourceVersionRepository.GetExternalContentDetails(resourceVersionId, userId); + if (viewModel != null) + { + viewModel.HostedContentUrl = $"{this.learningHubConfig.ContentServerUrl}/{viewModel.ExternalReference}/".ToLower(); + } + + return viewModel; + } /// /// The GetFileStatusDetailsAsync. @@ -667,6 +683,55 @@ public async Task> GetResourceLicencesAsync() return licenceList; } + /// + /// The get resource version by id async. + /// + /// The resourceVersionId. + /// The . + public async Task GetResourceVersionByIdAsync(int resourceVersionId) + { + var resourceVersion = await this.resourceVersionRepository.GetBasicByIdAsync(resourceVersionId); + + var vm = this.mapper.Map(resourceVersion); + + var nodeResources = await this.nodeResourceRepository.GetByResourceIdAsync(vm.ResourceId); + var draftNodeResources = nodeResources.Where(x => x.VersionStatusEnum == VersionStatusEnum.Draft).ToList(); + var publishedNodeResources = nodeResources.Where(x => x.VersionStatusEnum == VersionStatusEnum.Published || x.VersionStatusEnum == VersionStatusEnum.Unpublished).ToList(); + + if (draftNodeResources.Count() == 0) + { + if (publishedNodeResources != null && publishedNodeResources.Count == 1) + { + vm.NodeId = publishedNodeResources[0].NodeId; + vm.ResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(publishedNodeResources[0].NodeId); + } + else + { + vm.NodeId = 0; + vm.ResourceCatalogueId = 0; + } + } + else if (draftNodeResources.Count() > 1) + { + throw new Exception("Resource must belong to a single catalogue. ResourceVersionId: " + vm.ResourceVersionId.ToString() + ". ResourceId: " + vm.ResourceId.ToString() + ", VersionStatusEnum:" + vm.VersionStatusEnum.ToString()); + } + else + { + vm.NodeId = draftNodeResources[0].NodeId; + vm.ResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(draftNodeResources[0].NodeId); + } + + if (publishedNodeResources != null && publishedNodeResources.Count == 1) + { + vm.PublishedResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(publishedNodeResources[0].NodeId); + } + + vm.Flags = await this.GetResourceVersionFlagViewModels(resourceVersion.ResourceVersionFlag); + + return vm; + } + + /// /// The get resource version view model async. /// @@ -1267,54 +1332,6 @@ public async Task HasPublishedResourcesAsync(int userId) return await this.resourceRepository.UserHasPublishedResourcesAsync(userId); } - /// - /// The get resource version by id async. - /// - /// The resourceVersionId. - /// The . - public async Task GetResourceVersionByIdAsync(int resourceVersionId) - { - var resourceVersion = await this.resourceVersionRepository.GetBasicByIdAsync(resourceVersionId); - - var vm = this.mapper.Map(resourceVersion); - - var nodeResources = await this.nodeResourceRepository.GetByResourceIdAsync(vm.ResourceId); - var draftNodeResources = nodeResources.Where(x => x.VersionStatusEnum == VersionStatusEnum.Draft).ToList(); - var publishedNodeResources = nodeResources.Where(x => x.VersionStatusEnum == VersionStatusEnum.Published || x.VersionStatusEnum == VersionStatusEnum.Unpublished).ToList(); - - if (draftNodeResources.Count() == 0) - { - if (publishedNodeResources != null && publishedNodeResources.Count == 1) - { - vm.NodeId = publishedNodeResources[0].NodeId; - vm.ResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(publishedNodeResources[0].NodeId); - } - else - { - vm.NodeId = 0; - vm.ResourceCatalogueId = 0; - } - } - else if (draftNodeResources.Count() > 1) - { - throw new Exception("Resource must belong to a single catalogue. ResourceVersionId: " + vm.ResourceVersionId.ToString() + ". ResourceId: " + vm.ResourceId.ToString() + ", VersionStatusEnum:" + vm.VersionStatusEnum.ToString()); - } - else - { - vm.NodeId = draftNodeResources[0].NodeId; - vm.ResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(draftNodeResources[0].NodeId); - } - - if (publishedNodeResources.Count == 1) - { - vm.PublishedResourceCatalogueId = await this.nodePathRepository.GetCatalogueRootNodeId(publishedNodeResources[0].NodeId); - } - - vm.Flags = await this.GetResourceVersionFlagViewModels(resourceVersion.ResourceVersionFlag); - - return vm; - } - /// /// The save resource version flag async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index 9e8be7d55..f514d1f45 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -281,6 +281,17 @@ public async Task GetCaseResourceVersionAsync(int resourceVersionI return this.Ok(await this.resourceService.GetCaseDetailsByIdAsync(resourceVersionId)); } + /// + /// The GetExternalContentDetailsById. + /// + /// The resourceVersionId. + /// The . + [HttpGet("GetExternalContentDetailsById/{resourceVersionId}")] + public async Task GetScormContentDetailsById(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetExternalContentDetails(resourceVersionId, this.CurrentUserId.GetValueOrDefault())); + } + /// /// The GetFileStatusDetailsAsync. /// @@ -342,6 +353,17 @@ public async Task GetResourceLicences() return this.Ok(await this.resourceService.GetResourceLicencesAsync()); } + /// + /// Get specific ResourceVersion by Id. + /// + /// The resourceVersionId. + /// The . + [HttpGet("GetResourceVersion/{resourceVersionId}")] + public async Task GetResourceVersionAsync(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetResourceVersionByIdAsync(resourceVersionId)); + } + /// /// Get specific ResourceVersionViewModel by Id. /// @@ -798,7 +820,7 @@ public async Task DuplicateBlocks(DuplicateBlocksRequestModel dup /// The . [HttpPost] [Route("CreateResource")] - public async Task CreateResourceAsync(ResourceDetailViewModel viewModel) + public async Task CreateResourceAsync([FromBody] ResourceDetailViewModel viewModel) { var vr = await this.resourceService.CreateResourceAsync(viewModel, this.CurrentUserId.GetValueOrDefault()); if (vr.IsValid) @@ -891,7 +913,7 @@ public async Task SubmitResourceVersionForPublishAsync(PublishVie [HttpPost] [Authorize(Policy = "ReadWrite")] [Route("UpdateResourceVersion")] - public async Task UpdateResourceVersionAsync(ResourceDetailViewModel resourceDetailViewModel) + public async Task UpdateResourceVersionAsync([FromBody] ResourceDetailViewModel resourceDetailViewModel) { var vr = await this.resourceService.UpdateResourceVersionAsync(resourceDetailViewModel, this.CurrentUserId.GetValueOrDefault()); @@ -1093,7 +1115,7 @@ public async Task DeleteResourceAttributeFileAsync(FileDeleteRequ /// The . [HttpPost] [Route("UpdateAudioDetail")] - public async Task UpdateAudioDetailAsync(AudioUpdateRequestViewModel audioViewModel) + public async Task UpdateAudioDetailAsync([FromBody] AudioUpdateRequestViewModel audioViewModel) { var vr = await this.resourceService.UpdateAudioDetailAsync(audioViewModel, this.CurrentUserId.GetValueOrDefault()); @@ -1114,7 +1136,7 @@ public async Task UpdateAudioDetailAsync(AudioUpdateRequestViewMo /// The . [HttpPost] [Route("UpdateArticleDetail")] - public async Task UpdateArticleDetailAsync(ArticleUpdateRequestViewModel articleViewModel) + public async Task UpdateArticleDetailAsync([FromBody] ArticleUpdateRequestViewModel articleViewModel) { var vr = await this.resourceService.UpdateArticleDetailAsync(articleViewModel, this.CurrentUserId.GetValueOrDefault()); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index 741ead5fa..1fd6fa2d1 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -3554,6 +3554,31 @@ } } }, + "/Resource/GetExternalContentDetailsById/{resourceVersionId}": { + "get": { + "tags": [ + "Resource" + ], + "summary": "Get External content details of a resource by ResourceVersionId.", + "parameters": [ + { + "name": "resourceVersionId", + "in": "path", + "description": "The resourceVersionIdSystem.Int32.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/Resource/GetWeblinkDetails/{resourceVersionId}": { "get": { "tags": [ @@ -3682,6 +3707,31 @@ } } }, + "/Resource/GetResourceVersion/{resourceVersionId}": { + "get": { + "tags": [ + "Resource" + ], + "summary": "Get specific ResourceVersion by Id.", + "parameters": [ + { + "name": "resourceVersionId", + "in": "path", + "description": "The resourceVersionIdSystem.Int32.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/Resource/GetResourceVersionViewModel/{resourceVersionId}": { "get": { "tags": [ From 6463dfdac0ff73764202a00de7a8ddafda0220b6 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Thu, 19 Jun 2025 14:13:19 +0100 Subject: [PATCH 41/83] LH Navigation Update --- .../LearningHub.Nhs.OpenApi/Controllers/UserController.cs | 8 +++++++- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index 1fb149c79..9d2aaea2e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -299,7 +299,7 @@ private List> MenuItems(NavigationModel model) }, new Dictionary { - { "title", "My Contributions" }, + { "title", "My contributions" }, { "url", this.learningHubConfig.MyContributionsUrl }, { "visible", model.ShowMyContributions }, }, @@ -310,6 +310,12 @@ private List> MenuItems(NavigationModel model) { "visible", model.ShowMyBookmarks }, }, new Dictionary + { + { "title", "My learning" }, + { "url", this.learningHubConfig.MyLearningUrl }, + { "visible", model.ShowMyLearning }, + }, + new Dictionary { { "title", "Help" }, { "url", this.learningHubConfig.HelpUrl }, diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 804dc13f5..6ac065c23 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -98,7 +98,7 @@ "MyRecordsUrl": "", "NotificationsUrl": "/notification", "RegisterUrl": "/register", - "SignOutUrl": "", + "SignOutUrl": "/home/logout", "MyAccountUrl": "/myaccount", "BrowseCataloguesUrl": "/allcatalogue" }, From e8c4abb004e83383947d3a4393cfc92fb55f02f3 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Thu, 19 Jun 2025 15:58:35 +0100 Subject: [PATCH 42/83] lh update --- .../Services/IUserGroupService.cs | 16 +++++++ .../Services/NavigationPermissionService.cs | 12 +++-- .../Services/UserGroupService.cs | 45 +++++++++++++++++++ .../Startup.cs | 1 + 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs new file mode 100644 index 000000000..bd2f4e920 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserGroupService.cs @@ -0,0 +1,16 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + + /// + /// The UserGroupService interface. + /// + public interface IUserGroupService + { + /// + /// The GetRoleUserGroupDetailAsync. + /// + /// The . + Task UserHasCatalogueContributionPermission(int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs index 76e66812f..e0bef0a1d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs @@ -12,14 +12,17 @@ public class NavigationPermissionService : INavigationPermissionService { private readonly IResourceService resourceService; + private readonly IUserGroupService userGroupService; /// /// Initializes a new instance of the class. /// /// Resource service. - public NavigationPermissionService(IResourceService resourceService) + /// userGroup service. + public NavigationPermissionService(IResourceService resourceService, IUserGroupService userGroupService) { this.resourceService = resourceService; + this.userGroupService = userGroupService; } /// @@ -53,7 +56,7 @@ public async Task GetNavigationModelAsync(IPrincipal user, bool } else if (user.IsInRole("BlueUser")) { - return AuthenticatedBlueUser(controllerName); + return await AuthenticatedBlueUser(controllerName, user.Identity.GetCurrentUserId()); } else { @@ -114,12 +117,13 @@ private NavigationModel AuthenticatedAdministrator(string controllerName) /// The AuthenticatedBlueUser. /// /// The controller name. + /// The userId. /// The . - private NavigationModel AuthenticatedBlueUser(string controllerName) + private async Task AuthenticatedBlueUser(string controllerName, int userId) { return new NavigationModel() { - ShowMyContributions = true, + ShowMyContributions = await this.userGroupService.UserHasCatalogueContributionPermission(userId), ShowMyLearning = true, ShowMyBookmarks = true, ShowSearch = controllerName != "search" && controllerName != string.Empty, diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs new file mode 100644 index 000000000..205494eca --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Http; + + /// + /// The user group service. + /// + public class UserGroupService : IUserGroupService + { + + private readonly IRoleUserGroupRepository roleUserGroupRepository; + + /// + /// Initializes a new instance of the class. + /// + /// roleUserGroupRepository. + public UserGroupService(IRoleUserGroupRepository roleUserGroupRepository) + { + this.roleUserGroupRepository = roleUserGroupRepository; + } + + + /// + public async Task UserHasCatalogueContributionPermission(int userId) + { + var userRoleGroups = await this.roleUserGroupRepository.GetRoleUserGroupViewModelsByUserId(userId); + if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.LocalAdmin || r.RoleEnum == RoleEnum.Editor)) + { + return true; + } + + return false; + } + + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index f620c101b..b92103a2f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -31,6 +31,7 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); From 166e6a4f0847796eac3838ad2e8b035e4a083b3b Mon Sep 17 00:00:00 2001 From: Frank C <115793157+frank-hee@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:50:16 +0100 Subject: [PATCH 43/83] Moved all .nhsuk classes to nhsuk.scss. grouped and commented --- .../Styles/nhsuk/layout.scss | 439 --------------- LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss | 500 +++++++++++++++++- 2 files changed, 499 insertions(+), 440 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index 4cb2822fe..f8e150eeb 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -5,126 +5,11 @@ body { overflow-wrap: break-word; } -.nhsuk-header { - padding: 0 px2rem(32); -} - -.nhsuk-width-container.app-width-container { - max-width: px2rem(1208); - margin: 0 auto; - padding-left: px2rem(32); - padding-right: px2rem(32); -} - -.nhsuk-header .nhsuk-width-container.app-width-container { - max-width: px2rem(1144); - margin: 0 auto; -} - -.nhsuk-width-container.app-width-container.beta-banner { - padding: px2rem(8) px2rem(32); - max-width: px2rem(1208); - margin: 0 auto; -} - -.nhsuk-header .nhsuk-header__container::after { - content: none; -} - -.nhsuk-header__navigation.app-width-container { - max-width: px2rem(1144); -} - -.app-width-container--full { - margin: 0; - max-width: none -} .app-main-wrapper--no-padding { padding: 0 } -.nhsuk-header__container.app-width-container { - display: flex; - justify-content: space-between; - gap: 0 px2rem(24); - padding: px2rem(16) 0; -} - -.nhsuk-header__content { - display: flex; - align-items: center; - min-height: px2rem(40); - margin-left: auto; -} - -.nhsuk-header__logo { - flex: 1 0 0; -} - -.nhsuk-header__logo .nhsuk-header__link--service { - display: inline-flex; -} - -.nhsuk-header__service-name { - font-size: px2rem(19); -} - -.nhsuk-account__login { - font-size: px2rem(14); - float: right; - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - align-items: center; - gap: px2rem(24); -} - -.nhsuk-header__notification-dot { - position: absolute; - top: px2rem(8); - right: px2rem(-10); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: white; - min-width: px2rem(18); - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - padding: px2rem(1) px2rem(3) 0; -} - -.nhsuk-header__menu { - display: none; -} - -.nhsuk-header__search .nhsuk-search__input { - width: px2rem(260); -} - -.nhsuk-header__search { - .nhsuk-search__input { - width: px2rem(260); - - &::-moz-placeholder { - opacity: 1; - } - } - - #search > label.nhsuk-u-visually-hidden { - background-color: $nhsuk-white; - } -} - -.nhsuk-account__login--link, -.nhsuk-account__login--link:visited, -.nhsuk-account__login--link:hover { - color: #fff; -} - .beta-banner { background-color: $color_nhsuk-grey-5; color: $nhsuk-text-color; @@ -155,10 +40,6 @@ body { font-size: px2rem(16); } -.nhsuk-footer { - padding: px2rem(48) 0; -} - #header-dropdown-menu-control { opacity: 0; @@ -173,23 +54,6 @@ body { display: none; } -.nhsuk-header__break { - display: none; -} - -.nhsuk-header__mobile-only-nav { - display: none; -} - -.nhsuk-header__mobile-break { - display: none; -} - -.nhsuk-header__navigation-item--current { - a { - font-weight: bold; - } -} button[data-toggle="modal"] { color: #005eb8; @@ -254,138 +118,9 @@ button[data-toggle="modal"] { li.autosuggestion-option:last-of-type { border-bottom: none !important; } -/* large desktop */ -@media (min-width: px2rem(990)) { - - .nhsuk-header__navigation-item--current a { - border-bottom: 4px solid $nhsuk-grey-lighter; - font-weight: normal; - } - - .nhsuk-header__navigation-link { - position: relative; - } - - .nhsuk-header__navigation-item:last-child { - margin-right: 16px; - } -} /* small desktop */ @media (max-width: px2rem(989)) { - - .nhsuk-header__container { - flex-wrap: wrap; - } - - .nhsuk-header { - padding: 0; - } - - .nhsuk-header__container.app-width-container { - flex-wrap: wrap; - gap: 0 0; - padding: px2rem(16) px2rem(32); - } - - .nhsuk-header__link--service { - align-items: center; - -ms-flex-align: center; - margin-bottom: 0; - width: auto; - } - - .nhsuk-header__service-name { - padding-left: px2rem(16); - } - - .nhsuk-header__logo { - order: 0; - } - - .nhsuk-account__login { - order: 1; - margin-left: auto; - margin-right: 0px; - } - - .nhsuk-header__break { - display: block; - width: 100%; - height: px2rem(24); - order: 2 - } - - .nhsuk-header__search { - order: 3; - flex-grow: 1; - margin-left: 0; - margin-right: px2rem(24); - } - - .nhsuk-header__menu { - display: block; - position: relative; - order: 4; - flex: 0 0 px2rem(74); - } - - .nhsuk-header__navigation-list .nhsuk-header__navigation-item, - .nhsuk-header__navigation .nhsuk-header__navigation-title { - border-top: 1px solid $color_nhsuk-grey-4; - } - - .nhsuk-header__menu-toggle { - text-align: center; - margin: 0; - right: 0; - font-weight: 600; - } - - .nhsuk-header__search-form { - display: flex; - } - - .nhsuk-header__search .nhsuk-search__input { - flex: 1 0 0; - } - - #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block; - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(15); - left: px2rem(115); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: $nhsuk-white; - min-width: px2rem(18); - width: fit-content; - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - } - - .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { - left: px2rem(125); - } - - .nhsuk-header__menu-notification-dot { - position: absolute; - top: px2rem(-5); - right: px2rem(-6); - background: $nhsuk-error-color; - width: px2rem(12); - height: px2rem(12); - border-radius: px2rem(6); - box-shadow: 0 0 0 2px white; - z-index: 10; - } - .autosuggestion-menu { top: 100%; } @@ -394,192 +129,21 @@ li.autosuggestion-option:last-of-type { /* tablet */ @media (max-width: px2rem(768)) { - .nhsuk-width-container.app-width-container, - .nhsuk-width-container.app-width-container.beta-banner { - padding-left: px2rem(16); - padding-right: px2rem(16); - } - - .nhsuk-back-link { - padding: 0.5rem 0; - } - - .nhsuk-header__menu .nhsuk-header__not-mobile { - display: none; - } - .autosuggestion-menu { top: 100%; } - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - #header-mobile-search-control { display: block; opacity: 0; position: absolute; } - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } } /* mobile */ @media (max-width: px2rem(640)) { - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__logo { - max-width: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { - align-items: center; - justify-content: flex-end; - padding-bottom: px2rem(16); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - #header-mobile-search-control { display: block; opacity: 0; @@ -590,9 +154,6 @@ li.autosuggestion-option:last-of-type { display: block; } - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } .autosuggestion-menu { top: 100%; diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss index 7254e0901..47c9727f5 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss @@ -1 +1,499 @@ -@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; \ No newline at end of file +@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; + +// Overrides largely due to +// - Highly customised header +// - Use of full browser width hero images and full width colour bands +// - Beta banner +// +// Header customisation not needed with current frontend package +// +// - FGC 19/06/25 + + +.nhsuk-width-container.app-width-container { + max-width: px2rem(1208); + margin: 0 auto; + padding-left: px2rem(32); + padding-right: px2rem(32); +} + +// Header items + .nhsuk-header { + padding: 0 px2rem(32); + } + + .nhsuk-header .nhsuk-width-container.app-width-container { + max-width: px2rem(1144); + margin: 0 auto; + } + + .nhsuk-width-container.app-width-container.beta-banner { + padding: px2rem(8) px2rem(32); + max-width: px2rem(1208); + margin: 0 auto; + } + + .nhsuk-header .nhsuk-header__container::after { + content: none; + } + + .nhsuk-header__navigation.app-width-container { + max-width: px2rem(1144); + } + + .nhsuk-header__container.app-width-container { + display: flex; + justify-content: space-between; + gap: 0 px2rem(24); + padding: px2rem(16) 0; + } + + .nhsuk-header__logo { + flex: 1 0 0; + } + + .nhsuk-header__logo .nhsuk-header__link--service { + display: inline-flex; + } + + .nhsuk-header__service-name { + font-size: px2rem(19); + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(8); + right: px2rem(-10); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: white; + min-width: px2rem(18); + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + padding: px2rem(1) px2rem(3) 0; + } + + .nhsuk-header__menu { + display: none; + } + + .nhsuk-header__search .nhsuk-search__input { + width: px2rem(260); + } + + .nhsuk-header__search { + .nhsuk-search__input { + width: px2rem(260); + + &::-moz-placeholder { + opacity: 1; + } + } + + #search > label.nhsuk-u-visually-hidden { + background-color: $nhsuk-white; + } + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-only-nav { + display: none; + } + + .nhsuk-header__mobile-break { + display: none; + } + + .nhsuk-header__navigation-item--current { + a { + font-weight: bold; + } + } + + .nhsuk-account__login { + // also a header item + font-size: px2rem(14); + float: right; + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + gap: px2rem(24); + } + + .nhsuk-account__login--link, + .nhsuk-account__login--link:visited, + .nhsuk-account__login--link:hover { + // all header items + color: #fff; + } + + +// End of header items + + +.app-width-container--full { + // used to allow placement of hero (full width) images + margin: 0; + max-width: none +} + +.app-main-wrapper--no-padding { + // allowing hero image to touch headers + padding: 0 +} + +.nhsuk-footer { + padding: px2rem(48) 0; +} + + + +/* large desktop */ +@media (min-width: px2rem(990)) { + // entirely headers + + .nhsuk-header__navigation-item--current a { + border-bottom: 4px solid $nhsuk-grey-lighter; + font-weight: normal; + } + + .nhsuk-header__navigation-link { + position: relative; + } + + .nhsuk-header__navigation-item:last-child { + margin-right: 16px; + } +} + +/* small desktop */ +@media (max-width: px2rem(989)) { + + //entirely headers + + .nhsuk-header__container { + flex-wrap: wrap; + } + + .nhsuk-header { + padding: 0; + } + + .nhsuk-header__container.app-width-container { + flex-wrap: wrap; + gap: 0 0; + padding: px2rem(16) px2rem(32); + } + + .nhsuk-header__link--service { + align-items: center; + -ms-flex-align: center; + margin-bottom: 0; + width: auto; + } + + .nhsuk-header__service-name { + padding-left: px2rem(16); + } + + .nhsuk-header__logo { + order: 0; + } + + .nhsuk-header__break { + display: block; + width: 100%; + height: px2rem(24); + order: 2 + } + + .nhsuk-header__search { + order: 3; + flex-grow: 1; + margin-left: 0; + margin-right: px2rem(24); + } + + .nhsuk-header__menu { + display: block; + position: relative; + order: 4; + flex: 0 0 px2rem(74); + } + + .nhsuk-header__navigation-list .nhsuk-header__navigation-item, + .nhsuk-header__navigation .nhsuk-header__navigation-title { + border-top: 1px solid $color_nhsuk-grey-4; + } + + .nhsuk-header__menu-toggle { + text-align: center; + margin: 0; + right: 0; + font-weight: 600; + } + + .nhsuk-header__search-form { + display: flex; + } + + .nhsuk-header__search .nhsuk-search__input { + flex: 1 0 0; + } + + #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block; + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(15); + left: px2rem(115); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: $nhsuk-white; + min-width: px2rem(18); + width: fit-content; + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + } + + .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { + left: px2rem(125); + } + + .nhsuk-header__menu-notification-dot { + position: absolute; + top: px2rem(-5); + right: px2rem(-6); + background: $nhsuk-error-color; + width: px2rem(12); + height: px2rem(12); + border-radius: px2rem(6); + box-shadow: 0 0 0 2px white; + z-index: 10; + } + + .nhsuk-account__login { + // also part of the header + order: 1; + margin-left: auto; + margin-right: 0px; + } + +} + +@media (max-width: px2rem(768)) { + .nhsuk-width-container.app-width-container, + .nhsuk-width-container.app-width-container.beta-banner { + padding-left: px2rem(16); + padding-right: px2rem(16); + } + + // entirely headers from this point + + .nhsuk-back-link { + padding: 0.5rem 0; + } + + .nhsuk-header__menu .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } + +} + +@media (max-width: px2rem(640)) { + + //entirely headers + + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__logo { + max-width: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { + align-items: center; + justify-content: flex-end; + padding-bottom: px2rem(16); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } + + +} + + + + + + + + + + + + From 23cc87feeaf1f9bb66bcbb77654a1a1c6c54ccd7 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 19 Jun 2025 18:18:22 +0100 Subject: [PATCH 44/83] Revering cpm changes --- .../LearningHub.Nhs.AdminUI.csproj | 1274 +++++++++-------- Directory.Packages.props | 2 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 66 +- LearningHub.Nhs.WebUI.sln | 1 - .../LearningHub.Nhs.WebUI.csproj | 496 +++---- .../LearningHub.Nhs.OpenApi.Models.csproj | 39 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 46 +- ...earningHub.Nhs.OpenApi.Repositories.csproj | 58 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 46 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 74 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 57 +- .../LearningHub.NHS.OpenAPI.csproj | 79 +- ...ub.Nhs.ReportApi.Services.Interface.csproj | 51 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 84 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 64 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 46 +- .../LearningHub.Nhs.ReportApi.csproj | 37 +- .../LearningHub.Nhs.Api.csproj | 126 +- WebAPI/LearningHub.Nhs.API/web.config | 39 +- .../LearningHub.Nhs.Api.Shared.csproj | 33 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 62 +- ...earningHub.Nhs.Repository.Interface.csproj | 32 +- .../LearningHub.Nhs.Repository.csproj | 44 +- .../LearningHub.Nhs.Services.Interface.csproj | 55 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 87 +- .../LearningHub.Nhs.Services.csproj | 70 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 125 +- ...LearningHub.Nhs.Migration.Interface.csproj | 40 +- .../LearningHub.Nhs.Migration.Models.csproj | 37 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 49 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 64 +- .../LearningHub.Nhs.Migration.csproj | 54 +- 32 files changed, 1810 insertions(+), 1627 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index ce9830312..a0de54f8f 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -1,631 +1,645 @@ - - net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 - 31abd8b9-4223-4ff3-896b-a46530c9e15c - /subscriptions/57c55d5f-78c1-4373-a021-ff8357548f51/resourceGroups/LearningHubNhsUk-AdminUI-Prod-RG/providers/microsoft.insights/components/LearningHubNhsUk-AdminUI-Prod - true - true - x64 - - - - - - - - - - - - - - - - - - - - - <_ContentIncludedByDefault Remove="bundleconfig.json" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - \ No newline at end of file + + + net8.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 + 31abd8b9-4223-4ff3-896b-a46530c9e15c + /subscriptions/57c55d5f-78c1-4373-a021-ff8357548f51/resourceGroups/LearningHubNhsUk-AdminUI-Prod-RG/providers/microsoft.insights/components/LearningHubNhsUk-AdminUI-Prod + true + true + x64 + + + + + + + + + + + + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="bundleconfig.json" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 8bde0b032..59413f8ea 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ - true + false diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index e12ccd3f4..1569f9734 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -1,31 +1,37 @@ - - net8.0 - enable - enable - false - True - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - \ No newline at end of file + + + net8.0 + enable + enable + + false + + True + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/LearningHub.Nhs.WebUI.sln b/LearningHub.Nhs.WebUI.sln index 433e58877..55cce01f0 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props - nuget.config = nuget.config StyleCop.ruleset = StyleCop.ruleset EndProjectSection EndProject diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 24d3c72f8..dc2cf661d 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -1,242 +1,256 @@  - - net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 - InProcess - a2ecb5d2-cf13-4551-9cb6-3d86dfbcf8ef - true - true - x64 - true - - - Project - http://localhost:5001 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - Always - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - \ No newline at end of file + + + net8.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 + InProcess + a2ecb5d2-cf13-4551-9cb6-3d86dfbcf8ef + true + true + x64 + true + + + + Project + http://localhost:5001 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index baf3dee90..727560e3f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -1,18 +1,23 @@ - - net8.0 - true - enable - x64 - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + true + enable + x64 + + + + + + + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj index 1617cfa0d..cbab74f45 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/LearningHub.Nhs.OpenApi.Repositories.Interface.csproj @@ -1,21 +1,27 @@ - - net8.0 - LearningHub.Nhs.OpenApi.Repositories.Interface - enable - true - x64 - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + LearningHub.Nhs.OpenApi.Repositories.Interface + enable + true + x64 + + + + + + + + + + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj index effe5591f..009e83ef8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -1,27 +1,33 @@ - - net8.0 - LearningHub.Nhs.OpenApi.Repositories - enable - true - x64 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + LearningHub.Nhs.OpenApi.Repositories + enable + true + x64 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 274460624..aa019e18c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -1,21 +1,27 @@ - - net8.0 - enable - true - x64 - - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + enable + true + x64 + + + + + + + + + + + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index 465bda0cd..9002a0237 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -1,35 +1,41 @@ - - net8.0 - LearningHub.Nhs.OpenApi.Services - enable - true - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + LearningHub.Nhs.OpenApi.Services + enable + true + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 8974e0fdb..3efc63b33 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -1,28 +1,31 @@ - - net8.0 - false - enable - x64 - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - \ No newline at end of file + + + net8.0 + false + enable + x64 + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 0871850e4..dfa347ffe 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -1,37 +1,44 @@ - - enable - net8.0 - true - x64 - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - \ No newline at end of file + + + enable + net8.0 + true + x64 + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj index b8c9379fa..227e30bf8 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj @@ -1,23 +1,30 @@ - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj index 114d8ae76..3af90e39a 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj @@ -1,38 +1,46 @@ - - - net8.0 - enable - enable - false - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - \ No newline at end of file + + + + net8.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj index fe3bf575b..c6d03d856 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -1,29 +1,37 @@ - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj index b32e219f3..78f41769e 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -1,21 +1,27 @@ - - net8.0 - enable - enable - $(NoWarn),1573,1591,1712 - True - - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + enable + enable + $(NoWarn),1573,1591,1712 + True + + + + + + + + + + + + + + + + + + + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index 17b31c21d..f87bb47d5 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -1,31 +1,42 @@ + net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 enable enable False + + - - - - - - - + + + + + + + + + + + + + + + PreserveNewest @@ -33,12 +44,14 @@ PreserveNewest + PreserveNewest + - - \ No newline at end of file + + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 834f8d32c..3b09a079f 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -1,60 +1,68 @@  - - net8.0 - 1.0.0.0 - 1.0.0.0 - 1.0.0 - InProcess - 234b55ad-984b-47ae-9d7a-71e372e6ded7 - true - x64 - - - LearningHub.Nhs.Api.xml - - - - - - - Always - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + net8.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0 + InProcess + 234b55ad-984b-47ae-9d7a-71e372e6ded7 + true + x64 + + + + LearningHub.Nhs.Api.xml + + + + + + + + + Always + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.API/web.config b/WebAPI/LearningHub.Nhs.API/web.config index 8771970fe..798b63b5d 100644 --- a/WebAPI/LearningHub.Nhs.API/web.config +++ b/WebAPI/LearningHub.Nhs.API/web.config @@ -1,21 +1,26 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 1cfafdb8e..1963fd724 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -1,16 +1,19 @@ - - net8.0 - true - x64 - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index b024beaf5..e7a054e23 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -1,30 +1,34 @@ - - net8.0 - true - false - x64 - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - \ No newline at end of file + + + net8.0 + true + false + x64 + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index 91c49b25f..7e09ec673 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -1,23 +1,27 @@ + net8.0 true - x64 + x64 + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + - \ No newline at end of file + + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 836a83129..6da0f9200 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -1,21 +1,25 @@ - - net8.0 - true - x64 - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 636f10a2e..024756991 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -1,26 +1,31 @@ - - net8.0 - true - x64 - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index 08a04e008..6b6743743 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -1,42 +1,47 @@ - - net8.0 - true - false - x64 - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - PreserveNewest - - - \ No newline at end of file + + + net8.0 + true + false + x64 + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 38c383d6c..44f8119cb 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -1,34 +1,38 @@ - - net8.0 - true - x64 - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 0a2e1e509..73e18184e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -1,60 +1,67 @@ - - Exe - net8.0 - 7.3 - true - x64 - - - - - - - PreserveNewest - true - PreserveNewest - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - Always - - - PreserveNewest - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - \ No newline at end of file + + + Exe + net8.0 + 7.3 + true + x64 + + + + + + + + + PreserveNewest + true + PreserveNewest + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + Always + + + PreserveNewest + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 75f4e5e58..7b945c483 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -1,19 +1,23 @@ - - net8.0 - true - x64 - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 9801da32e..189b0348e 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -1,18 +1,21 @@ - - net8.0 - true - x64 - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index e21f4c7a4..090dce6fc 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -1,23 +1,28 @@ - - net8.0 - true - x64 - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index 48f23a40b..a9b7683f7 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -1,31 +1,35 @@ - - net8.0 - true - false - x64 - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - \ No newline at end of file + + + net8.0 + true + false + x64 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 9082047ec..71195c130 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -1,26 +1,30 @@ - - net8.0 - true - x64 - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - \ No newline at end of file + + + net8.0 + true + x64 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + From f63c941349f3b99e3961533041da52f2200c7f96 Mon Sep 17 00:00:00 2001 From: Arunima George Date: Fri, 20 Jun 2025 08:44:03 +0100 Subject: [PATCH 45/83] TD-5703: Fixed issues in auto suggestion and search feedback --- LearningHub.Nhs.WebUI/Controllers/SearchController.cs | 2 +- LearningHub.Nhs.WebUI/Views/Search/_SearchBar.cshtml | 4 ++-- .../Views/Shared/Components/NavigationItems/Searchbar.cshtml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs index ff1afba0d..a7b53fb11 100644 --- a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs @@ -81,7 +81,7 @@ public async Task Index(SearchRequestViewModel search, bool noSor if (filterApplied) { - await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ApplyFilter, searchResult.ResourceSearchResult.TotalHits); + await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ApplyFilter, searchResult.ResourceSearchResult?.TotalHits ?? 0); } if (noSortFilterError) diff --git a/LearningHub.Nhs.WebUI/Views/Search/_SearchBar.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_SearchBar.cshtml index a0139e3eb..da4a475e9 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_SearchBar.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_SearchBar.cshtml @@ -48,8 +48,8 @@ function autocomplete(input, minLength) { if (input != null) { input.addEventListener("input", function () { - var val = this.value; - if (val.length < minLength) { + var val = this.value.trimStart(); + if (val.length < minLength || val.trim() === "") { closeAllLists(); return false; } diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Searchbar.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Searchbar.cshtml index 8f7f8a738..f3a69f8e6 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Searchbar.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Components/NavigationItems/Searchbar.cshtml @@ -72,8 +72,8 @@ function autocomplete(input, minLength) { if (input != null) { input.addEventListener("input", function () { - var val = this.value; - if (val.length < minLength) { + var val = this.value.trimStart(); + if (val.length < minLength || val.trim() === "") { closeAllLists(); return false; } From 75e3e7afbd3d85062eb93c36dd0aaff332dd0b4e Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Fri, 20 Jun 2025 12:04:18 +0100 Subject: [PATCH 46/83] TD-5502: Replace Web API layer with Open API Layer -Admin Operations-Catalogue Creation/Catalogue Edit/ Edit Folder Structure/resource Transfer and unpublish/resource details/user management- SIT fixes --- .../Services/CatalogueService.cs | 4 +- .../Services/ContentService.cs | 2 +- .../Services/UserGroupService.cs | 4 +- LearningHub.Nhs.WebUI/web.config | 44 +++++++++++-------- .../Services/CatalogueService.cs | 2 +- .../Controllers/CatalogueController.cs | 6 +-- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs index b2a082dc6..0ce6c58a5 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs @@ -25,11 +25,13 @@ public class CatalogueService : BaseService, ICatalogueService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The Open Api Http Client. /// The openApiFacade. public CatalogueService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, IOpenApiFacade openApiFacade) - : base(learningHubHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { this.facade = openApiFacade; } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs index cc792c228..ec983fffd 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/ContentService.cs @@ -37,7 +37,7 @@ public class ContentService : BaseService, IContentService /// The fileService. /// azureMediaService. public ContentService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IFileService fileService, IAzureMediaService azureMediaService) - : base(learningHubHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { this.fileService = fileService; this.azureMediaService = azureMediaService; diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs index a9a7a3133..0a941c761 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/UserGroupService.cs @@ -32,15 +32,17 @@ public class UserGroupService : BaseService, IUserGroupService /// Initializes a new instance of the class. /// /// The learningHubHttpClient. + /// The Open Api Http Client. /// The http context accessor. /// The cacheService. /// The roleService. public UserGroupService( ILearningHubHttpClient learningHubHttpClient, + IOpenApiHttpClient openApiHttpClient, ICacheService cacheService, IRoleService roleService, IHttpContextAccessor contextAccessor) - : base(learningHubHttpClient) + : base(learningHubHttpClient, openApiHttpClient) { this.contextAccessor = contextAccessor; this.cacheService = cacheService; diff --git a/LearningHub.Nhs.WebUI/web.config b/LearningHub.Nhs.WebUI/web.config index 837247997..eb1f7ff1e 100644 --- a/LearningHub.Nhs.WebUI/web.config +++ b/LearningHub.Nhs.WebUI/web.config @@ -1,21 +1,29 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index f75c0616e..53810f5d1 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -189,7 +189,7 @@ public CatalogueBasicViewModel GetBasicCatalogue(int catalogueNodeId) /// /// The searchTerm. /// The catalogues. - public List GetCatalogues(string searchTerm) + public List GetCatalogues(string? searchTerm) { IQueryable catalogueVersions = this.catalogueNodeVersionRepository.GetAll() .Include(x => x.Keywords) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 3d6e330ff..55b812fdb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -57,7 +57,7 @@ public async Task GetAllCataloguesAsync(string filterChar = null) /// The catalogues. [HttpGet] [Route("Catalogues")] - public IActionResult GetCatalogues(string searchTerm) + public IActionResult GetCatalogues([FromQuery] string? searchTerm) { var catalogues = this.catalogueService.GetCatalogues(searchTerm); return this.Ok(catalogues); @@ -152,7 +152,7 @@ public IActionResult GetCataloguesForCurrentUser() /// The actionResult. [HttpPost] [Route("Catalogues")] - public async Task CreateCatalogue(CatalogueViewModel viewModel) + public async Task CreateCatalogue([FromBody] CatalogueViewModel viewModel) { try { @@ -237,7 +237,7 @@ public async Task AccessRequest(int accessRequestId) /// The updated catalogue. [HttpPut] [Route("Catalogues")] - public async Task UpdateCatalogue(CatalogueViewModel viewModel) + public async Task UpdateCatalogue([FromBody] CatalogueViewModel viewModel) { try { From ad892e76a9b1e14150765a8bd9d1d42c497e8914 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Fri, 20 Jun 2025 12:06:55 +0100 Subject: [PATCH 47/83] Reverted the config changes --- LearningHub.Nhs.WebUI/web.config | 44 +++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/LearningHub.Nhs.WebUI/web.config b/LearningHub.Nhs.WebUI/web.config index eb1f7ff1e..837247997 100644 --- a/LearningHub.Nhs.WebUI/web.config +++ b/LearningHub.Nhs.WebUI/web.config @@ -1,29 +1,21 @@  - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - \ No newline at end of file + From 87b37888347b7dbdd7cee8751bb2621ff2d525aa Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Mon, 23 Jun 2025 08:00:45 +0100 Subject: [PATCH 48/83] Changed the LH menu item order --- .../Controllers/UserController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index 9d2aaea2e..6e5fe2591 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -298,6 +298,12 @@ private List> MenuItems(NavigationModel model) { "visible", model.ShowBrowseCatalogues }, }, new Dictionary + { + { "title", "My learning" }, + { "url", this.learningHubConfig.MyLearningUrl }, + { "visible", model.ShowMyLearning }, + }, + new Dictionary { { "title", "My contributions" }, { "url", this.learningHubConfig.MyContributionsUrl }, @@ -310,12 +316,6 @@ private List> MenuItems(NavigationModel model) { "visible", model.ShowMyBookmarks }, }, new Dictionary - { - { "title", "My learning" }, - { "url", this.learningHubConfig.MyLearningUrl }, - { "visible", model.ShowMyLearning }, - }, - new Dictionary { { "title", "Help" }, { "url", this.learningHubConfig.HelpUrl }, From 31c677ad10891c72cf9740657288646af69e23ae Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 23 Jun 2025 09:36:05 +0100 Subject: [PATCH 49/83] Removed optional referenece from the service class --- AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs index 0ce6c58a5..2ac0088d9 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/CatalogueService.cs @@ -31,7 +31,7 @@ public CatalogueService( ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IOpenApiFacade openApiFacade) - : base(learningHubHttpClient, openApiHttpClient) + : base(learningHubHttpClient) { this.facade = openApiFacade; } From 448c0c81c513402cac4234f2529eee78a50cec4b Mon Sep 17 00:00:00 2001 From: Frank C <115793157+frank-hee@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:55:07 +0100 Subject: [PATCH 50/83] Removed unnecessary whitespace at end of nhsuk.css --- LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss index 47c9727f5..40c7e1095 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss @@ -482,18 +482,4 @@ .nhsuk-width-container.nhsuk-header__container.app-width-container { padding-bottom: 0; } - - -} - - - - - - - - - - - - +} \ No newline at end of file From cea2f79f58870b55461f12969c10ec71ce38a66b Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 23 Jun 2025 17:14:05 +0100 Subject: [PATCH 51/83] TD-5499: Open API replacement- To fiox the LMS connection issue on SCORM package launching --- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 13dc2665a..bba6fcb9b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -1,4 +1,5 @@ { + "Environment": "", "Logging": { "LogLevel": { "Default": "Trace", From c38af428829d862a1231baa1bd1c2ea577c5e518 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Tue, 24 Jun 2025 11:59:16 +0100 Subject: [PATCH 52/83] Userbookmark bugfix --- .../LearningHub.Nhs.OpenApi.Repositories/Map/BaseEntityMap.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/BaseEntityMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/BaseEntityMap.cs index 8cc109be5..fefa89af7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/BaseEntityMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/BaseEntityMap.cs @@ -38,11 +38,13 @@ public void Map(ModelBuilder builder) && typeof(TEntityType) != typeof(EmbeddedResourceVersion) && typeof(TEntityType) != typeof(EquipmentResourceVersion) && typeof(TEntityType) != typeof(GenericFileResourceVersion) + && typeof(TEntityType) != typeof(HtmlResourceVersion) && typeof(TEntityType) != typeof(ImageResourceVersion) && typeof(TEntityType) != typeof(VideoResourceVersion) && typeof(TEntityType) != typeof(WebLinkResourceVersion) && typeof(TEntityType) != typeof(ResourceLicence) - && typeof(TEntityType) != typeof(User)) + && typeof(TEntityType) != typeof(User) + && typeof(TEntityType) != typeof(UserBookmark)) { builder.Entity().HasQueryFilter(e => !e.Deleted); } From 73aa0c2b69f0d38f2dcbd799b95078afbf998bd9 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 25 Jun 2025 09:41:35 +0100 Subject: [PATCH 53/83] TD-5708:Users Cannot Upload files on Test and Live Learning Hub Due to Password Prompt Loop --- LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts index 9f50357fc..ab81695ea 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts @@ -67,7 +67,8 @@ const IsSystemAdmin = async function (): Promise { }; const IsValidUser = async function (currentPassword: string): Promise { - var IsValidUser = `/api/User/ConfirmPassword/${currentPassword}`; + let encodedPassword = encodeURIComponent(currentPassword); + var IsValidUser = `/api/User/ConfirmPassword/${encodedPassword}`; return await AxiosWrapper.axios.get(IsValidUser) .then(response => { return response.data; From 2049905b039f2d6f8c3ff05a7ea72a7f12e2699c Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 25 Jun 2025 16:07:59 +0100 Subject: [PATCH 54/83] TD-5715: My accessed learning tray - navigation link doesn't work --- LearningHub.Nhs.WebUI/Controllers/HomeController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 18e3b82fe..6d578a339 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -280,6 +280,10 @@ public async Task LoadPage(string dashBoardTray = "my-learning", Catalogues = new Nhs.Models.Dashboard.DashboardCatalogueResponseViewModel { Type = catalogueDashBoard }, }; + var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; + this.ViewBag.EnableMoodle = enableMoodle; + this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0; + bool isAjax = this.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; if (isAjax) From 0c3009f8889559c50b59e13f4cf3f3a6860ada5b Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 26 Jun 2025 17:15:37 +0100 Subject: [PATCH 55/83] TD-5665: Community Contribution Suspend --- .../components/CatalogueSelectorAccordion.vue | 1 - .../Scripts/vuesrc/contribute/CatalogueSelect.vue | 3 +-- LearningHub.Nhs.WebUI/Services/UserGroupService.cs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue index fcd1e60a3..087d14625 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue @@ -20,7 +20,6 @@

You can contribute a resource as an editor of a catalogue or in your own name. - To contribute a resource in your own name, select Community contributions from the drop down menu.

You can manage all resources that you have contributed in the My contributions area. diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue index 379e9572a..3f5c7400d 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue @@ -26,8 +26,7 @@ A catalogue is a curated set of resources that has its own web page.

- You can contribute a resource as an editor of a catalogue or in your own name. - To contribute a resource in your own name, select Community contributions from the drop down menu. + You can contribute a resource as an editor of a catalogue or in your own name.

You can manage all resources that you have contributed in the My contributions area. diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 03581cb38..01db066e4 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -62,7 +62,7 @@ public async Task> GetRoleUserGroupDetailForUserAsy public async Task UserHasCatalogueContributionPermission() { var userRoleGroups = await this.GetRoleUserGroupDetailAsync(); - if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.LocalAdmin || r.RoleEnum == RoleEnum.Editor)) + if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.Editor)) { return true; } From 09c49db5b936535a6e52235d0d2fd659d8487cdb Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 26 Jun 2025 17:24:59 +0100 Subject: [PATCH 56/83] Corrected the sentence --- .../components/CatalogueSelectorAccordion.vue | 2 +- .../Scripts/vuesrc/contribute/CatalogueSelect.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue index 087d14625..e1ba6eeae 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/components/CatalogueSelectorAccordion.vue @@ -19,7 +19,7 @@ A catalogue is a curated set of resources that has its own web page.

- You can contribute a resource as an editor of a catalogue or in your own name. + You can contribute a resource as an editor of a catalogue.

You can manage all resources that you have contributed in the My contributions area. diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue index 3f5c7400d..6d942b072 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/CatalogueSelect.vue @@ -26,7 +26,7 @@ A catalogue is a curated set of resources that has its own web page.

- You can contribute a resource as an editor of a catalogue or in your own name. + You can contribute a resource as an editor of a catalogue.

You can manage all resources that you have contributed in the My contributions area. From 98bd92a5dd06b7052db15ab436c3504c5014d276 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Fri, 27 Jun 2025 11:54:37 +0100 Subject: [PATCH 57/83] Search Feedback fix --- LearningHub.Nhs.WebUI/Services/SearchService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.WebUI/Services/SearchService.cs b/LearningHub.Nhs.WebUI/Services/SearchService.cs index f0bf36ccf..578a5861f 100644 --- a/LearningHub.Nhs.WebUI/Services/SearchService.cs +++ b/LearningHub.Nhs.WebUI/Services/SearchService.cs @@ -512,7 +512,7 @@ public async Task SubmitFeedbackAsync(SearchFeedBackModel model) int createId = 0; var client = await this.OpenApiHttpClient.GetClientAsync(); - var request = this.settings.OpenApiUrl + "Search/SubmitFeedback"; + var request = $"Search/SubmitFeedback"; var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); From 7af1e40fe0bb31afc3c09be17c651f38b2f9dbef Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Fri, 27 Jun 2025 15:02:33 +0100 Subject: [PATCH 58/83] TD-5708: Password validation is failing for the users with password '&; --- .../Controllers/Api/UserController.cs | 10 +++++----- .../Scripts/vuesrc/data/user.ts | 17 ++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/UserController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/UserController.cs index 35294cc11..1f5330bbe 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/UserController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/UserController.cs @@ -102,13 +102,13 @@ public async Task CheckUserRole() ///

/// to check user password is correct. /// - /// The currentPassword. + /// The currentPassword. /// The . - [HttpGet] - [Route("ConfirmPassword/{currentPassword}")] - public async Task ConfirmPassword(string currentPassword) + [HttpPost] + [Route("ConfirmPassword")] + public async Task ConfirmPassword([FromBody] PasswordUpdateModel password) { - string passwordHash = this.userService.Base64MD5HashDigest(currentPassword); + string passwordHash = this.userService.Base64MD5HashDigest(password.PasswordHash); var userPersonalDetails = await this.userService.GetCurrentUserPersonalDetailsAsync(); if (userPersonalDetails != null && userPersonalDetails.PasswordHash == passwordHash) { diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts index ab81695ea..e38fd4a08 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/user.ts @@ -67,16 +67,15 @@ const IsSystemAdmin = async function (): Promise { }; const IsValidUser = async function (currentPassword: string): Promise { - let encodedPassword = encodeURIComponent(currentPassword); - var IsValidUser = `/api/User/ConfirmPassword/${encodedPassword}`; - return await AxiosWrapper.axios.get(IsValidUser) - .then(response => { - return response.data; - }) - .catch(e => { - console.log('IsValidUser:' + e); - throw e; + try { + const response = await AxiosWrapper.axios.post('/api/User/ConfirmPassword', { + PasswordHash: currentPassword }); + return response.data; + } catch (e) { + console.error('IsValidUser:', e); + throw e; + } }; const getCurrentUserBasicDetails = async function (): Promise { From dd65df07627b1bbc77dcb6e9c97c517299895367 Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Tue, 1 Jul 2025 11:39:33 +0100 Subject: [PATCH 59/83] db mapping update --- .../EntityFramework/ServiceMappings.cs | 6 +- .../Services/ResourceService.cs | 117 +++++++++--------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 2384dc4de..a3a35abcc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -10,7 +10,8 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy; using LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Map.Messaging; - using LearningHub.Nhs.OpenApi.Repositories.Map.Resources; + using LearningHub.Nhs.OpenApi.Repositories.Map.Migrations; + using LearningHub.Nhs.OpenApi.Repositories.Map.Resources; using LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -179,6 +180,9 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // External services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index 07a55e537..caa665259 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -112,63 +112,66 @@ public class ResourceService : IResourceService /// Initializes a new instance of the class. /// The search service. ///
- /// Logger. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The . - /// - /// - /// - /// - /// - /// - /// - /// - /// The . - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// + /// Initializes a new instance of the class. + /// + /// The logger instance. + /// The repository for web link resource versions. + /// The repository for case resource versions. + /// The repository for SCORM resource versions. + /// The repository for generic file resource versions. + /// The repository for all resource versions. + /// The repository for HTML resource versions. + /// The mapper for object-object mapping. + /// The repository for file storage and retrieval. + /// The Azure configuration settings. + /// The Learning Hub configuration settings. + /// The service to manage user profiles. + /// The repository for flags on resource versions. + /// The repository for article resource versions. + /// The repository for audio resource versions. + /// The repository for video resource versions. + /// The repository for assessment resource versions. + /// The repository for resource licences. + /// The repository for resource references. + /// The repository for user acceptance of resource versions. + /// The repository for catalogue node versions. + /// The service for caching data. + /// The service for resource search functionality. + /// The service to manage catalogues. + /// The repository for node-resource relationships. + /// The repository for node paths. + /// The service for user management. + /// The repository for nodes. + /// The service for syncing resources. + /// The repository for synced resources. + /// The repository for resource version events. + /// The database context instance. + /// The instance. + /// The service for file type operations. + /// The repository for block collections. + /// The service for internal system operations. + /// The repository for authors of resource versions. + /// The repository for file chunk details. + /// The service for queue communication. + /// The instance. + /// The repository for resource version providers. + /// The service for managing providers. + /// The repository for article resource version files. + /// The repository for publications. + /// The repository for migration sources. + /// The repository for question blocks. + /// The repository for video resources. + /// The repository for whole-slide images. + /// The repository for embedded resource versions. + /// The repository for equipment resource versions. + /// The repository for image resource versions. + /// The repository for bookmarks. + /// The repository for assessment activity-question matches. + /// The repository for resource version keywords. + /// The repository for validation results of resource versions. + + public ResourceService(ILearningHubService learningHubService, IFileTypeService fileTypeService, IBlockCollectionRepository blockCollectionRepository, IInternalSystemService internalSystemService, IResourceVersionAuthorRepository resourceVersionAuthorRepository, IFileChunkDetailRepository fileChunkDetailRepository, IQueueCommunicatorService queueCommunicatorService, IResourceRepository resourceRepository, IResourceVersionProviderRepository resourceVersionProviderRepository, IProviderService providerService, IArticleResourceVersionFileRepository articleResourceVersionFileRepository, IPublicationRepository publicationRepository, IMigrationSourceRepository migrationSourceRepository, IQuestionBlockRepository questionBlockRepository, IVideoRepository videoRepository, IWholeSlideImageRepository wholeSlideImageRepository, IEmbeddedResourceVersionRepository embeddedResourceVersionRepository, IEquipmentResourceVersionRepository equipmentResourceVersionRepository, IImageResourceVersionRepository imageResourceVersionRepository, IBookmarkRepository bookmarkRepository, IAssessmentResourceActivityMatchQuestionRepository assessmentResourceActivityMatchQuestionRepository, IResourceVersionKeywordRepository resourceVersionKeywordRepository, IResourceVersionValidationResultRepository resourceVersionValidationResultRepository, ILogger logger, IWebLinkResourceVersionRepository webLinkResourceVersionRepository, ICaseResourceVersionRepository caseResourceVersionRepository, IScormResourceVersionRepository scormResourceVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, IResourceVersionRepository resourceVersionRepository, IHtmlResourceVersionRepository htmlResourceVersionRepository, IMapper mapper, IFileRepository fileRepository, IOptions azureConfig, IOptions learningHubConfig, IUserProfileService userProfileService, IResourceVersionFlagRepository resourceVersionFlagRepository, IArticleResourceVersionRepository articleResourceVersionRepository, IAudioResourceVersionRepository audioResourceVersionRepository, IVideoResourceVersionRepository videoResourceVersionRepository, IAssessmentResourceVersionRepository assessmentResourceVersionRepository, IResourceLicenceRepository resourceLicenceRepository, IResourceReferenceRepository resourceReferenceRepository, IResourceVersionUserAcceptanceRepository resourceVersionUserAcceptanceRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, ICachingService cachingService, ISearchService searchService, ICatalogueService catalogueService, INodeResourceRepository nodeResourceRepository, INodePathRepository nodePathRepository, IUserService userService, INodeRepository nodeRepository, IResourceSyncService resourceSyncService, IResourceSyncRepository resourceSyncRepository, IResourceVersionEventRepository resourceVersionEventRepository, LearningHubDbContext dbContext) { this.learningHubService = learningHubService; From 199a26739b0663a501b04a26a5a832af5ff050f7 Mon Sep 17 00:00:00 2001 From: Oluwatobi Awe Date: Tue, 1 Jul 2025 14:03:34 +0100 Subject: [PATCH 60/83] Addition of a missing endpoint --- .../Services/IResourceService.cs | 8 +++++++ .../Services/ResourceService.cs | 19 +++++++++++++++ .../Controllers/ResourceController.cs | 24 ++++++++++++++----- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index 2a83eed80..e8427c4a6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -211,6 +211,14 @@ Task GetAssessmentProgress( /// The . List GetContributions(int userId, ResourceContributionsRequestViewModel resourceContributionsRequestViewModel, bool readOnly); + /// + /// The get my contributions view model async. + /// + /// The userId. + /// The myContributionsRequestViewModel. + /// The . + List GetMyContributions(int userId, MyContributionsRequestViewModel myContributionsRequestViewModel); + /// /// The get my resource view model async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index caa665259..c14d04edc 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -4394,6 +4394,25 @@ public List GetContributions(int userId, Resou return models; } + /// + /// The get my contributions view model async. + /// + /// The userId. + /// The myContributionsRequestViewModel. + /// The . + public List GetMyContributions(int userId, MyContributionsRequestViewModel myContributionsRequestViewModel) + { + var resourceVersions = this.resourceVersionRepository.GetAll() + .Where(rv => !rv.Deleted && + rv.CreateUserId == userId && + rv.Resource.ResourceTypeEnum == myContributionsRequestViewModel.ResourceType && + rv.VersionStatusEnum == myContributionsRequestViewModel.Status); + + var models = this.mapper.Map>(resourceVersions); + return models; + } + + /// /// The get my resource view model async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index f514d1f45..98ae2417c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -701,12 +701,24 @@ public ActionResult GetMyContributionsAsync(ResourceContributionsRequestViewMode return this.Ok(this.resourceService.GetContributions(this.CurrentUserId.GetValueOrDefault(), resourceContributionsRequestViewModel, this.HttpContext.User.IsInRole("ReadOnly"))); } - - /// - /// Returns Resource Cards. - /// - /// The . - [HttpGet] + /// + /// Returns the requested contributions. + /// + /// The myContributionsRequestViewModel. + /// The . + [HttpPost] + [Route("GetMyContributions")] + public ActionResult GetMyContributions(MyContributionsRequestViewModel myContributionsRequestViewModel) + { + return this.Ok(this.resourceService.GetMyContributions(this.CurrentUserId.GetValueOrDefault(), myContributionsRequestViewModel)); + } + + + /// + /// Returns Resource Cards. + /// + /// The . + [HttpGet] [Route("GetMyResourceViewModel")] public async Task GetMyResourceViewModelAsync() { From 102ebab31fb9ed2a421f57d4d8de4e0ffbae0ca0 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 2 Jul 2025 12:02:27 +0100 Subject: [PATCH 61/83] Reverted the commented code --- LearningHub.Nhs.WebUI/Services/UserGroupService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 01db066e4..c9b196bfc 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -47,8 +47,7 @@ public UserGroupService( public async Task> GetRoleUserGroupDetailAsync() { var cacheKey = $"{this.contextAccessor.HttpContext.User.Identity.GetCurrentUserId()}:AllRolesWithPermissions"; - ////return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); - return await this.FetchRoleUserGroupDetailAsync(); + return await this.cacheService.GetOrFetchAsync(cacheKey, () => this.FetchRoleUserGroupDetailAsync()); } /// From a383b7290eedf63d794acb3fd22523e9b55c811d Mon Sep 17 00:00:00 2001 From: binon Date: Fri, 4 Jul 2025 12:54:01 +0100 Subject: [PATCH 62/83] Update continuous-integration-workflow.yml --- .../continuous-integration-workflow.yml | 96 ++++--------------- 1 file changed, 16 insertions(+), 80 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 24c2d6891..94fca85b0 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -9,67 +9,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v3 - - - name: Modify web.config files in all apps - shell: pwsh - run: | - $webConfigPaths = @( - "${{ github.workspace }}\AdminUI\LearningHub.Nhs.AdminUI\web.config", - "${{ github.workspace }}\WebAPI\LearningHub.Nhs.Api\web.config", - "${{ github.workspace }}\LearningHub.Nhs.WebUI\web.config" - ) - - foreach ($path in $webConfigPaths) { - if (Test-Path $path) { - Write-Host "Modifying $path" - [xml]$config = Get-Content $path - - if (-not $config.configuration.'system.webServer') { - $systemWebServer = $config.CreateElement("system.webServer") - $config.configuration.AppendChild($systemWebServer) | Out-Null - } else { - $systemWebServer = $config.configuration.'system.webServer' - } - - if (-not $systemWebServer.httpProtocol) { - $httpProtocol = $config.CreateElement("httpProtocol") - $systemWebServer.AppendChild($httpProtocol) | Out-Null - } else { - $httpProtocol = $systemWebServer.httpProtocol - } - - if (-not $httpProtocol.customHeaders) { - $customHeaders = $config.CreateElement("customHeaders") - $httpProtocol.AppendChild($customHeaders) | Out-Null - } else { - $customHeaders = $httpProtocol.customHeaders - } - - foreach ($name in @("X-Powered-By", "Server")) { - $removeNode = $config.CreateElement("remove") - $removeNode.SetAttribute("name", $name) - $customHeaders.AppendChild($removeNode) | Out-Null - } - - if (-not $systemWebServer.security) { - $security = $config.CreateElement("security") - $systemWebServer.AppendChild($security) | Out-Null - } else { - $security = $systemWebServer.security - } - - if (-not $security.requestFiltering) { - $requestFiltering = $config.CreateElement("requestFiltering") - $requestFiltering.SetAttribute("removeServerHeader", "true") - $security.AppendChild($requestFiltering) | Out-Null - } - - $config.Save($path) - } else { - Write-Host "File not found: $path" - } - } - + - name: Setup .NET Core SDK 8.0 uses: actions/setup-dotnet@v3 with: @@ -80,30 +20,27 @@ jobs: dotnet nuget remove source LearningHubFeed || true dotnet nuget add source 'https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json' --name 'LearningHubFeed' --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text - - name: Use Node 20 with Yarn + - name: Use Node 14 uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' - - - name: Upgrade npm to the latest version - run: npm install -g npm@6.14.8 + node-version: '14' + cache: 'npm' - - name: Typescript install WebUI - run: yarn install --network-timeout 600000 --frozen-lockfile + - name: npm install WebUI + run: npm install working-directory: ./LearningHub.Nhs.WebUI - - - name: Typescript build WebUI - run: yarn build:webpack + + - name: npm build WebUI + run: npm run build:webpack working-directory: ./LearningHub.Nhs.WebUI - - name: Typescript install AdminUI - run: yarn install + - name: npm install AdminUI + run: npm install working-directory: ./AdminUI/LearningHub.Nhs.AdminUI - - - name: Typescript build AdminUI - run: yarn build:webpack - working-directory: ./AdminUI/LearningHub.Nhs.AdminUI + + - name: npm build AdminUI + run: npm run build:webpack + working-directory: ./AdminUI/LearningHub.Nhs.AdminUI - name: Setup MSBuild uses: microsoft/setup-msbuild@v1.0.3 @@ -183,5 +120,4 @@ jobs: } # - name: Test - # run: dotnet test ${{ env.BuildParameters.TestProjects }} - + # run: dotnet test ${{ env.BuildParameters.TestProjects }} From 8d0f9a7160e6119a42173b4b7114d34b36444455 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:51:18 +0100 Subject: [PATCH 63/83] fixes --- .../Services/ICatalogueService.cs | 8 ++++++++ .../Services/CatalogueService.cs | 12 +++++++++++ .../Controllers/CatalogueController.cs | 20 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs index f763808ef..af8ba8562 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICatalogueService.cs @@ -238,5 +238,13 @@ public interface ICatalogueService /// The allcatalogue result based on letters. Task GetAllCataloguesAsync(string filterChar, int userId); + /// + /// The UpdateCatalogueOwnerAsync. + /// + /// The userId. + /// The catalogue owner. + /// The catalogue view model. + Task UpdateCatalogueOwnerAsync(int userId, CatalogueOwnerViewModel catalogueOwner); + } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 53810f5d1..9c78cdb0d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -1190,6 +1190,18 @@ private LearningHubValidationResult ValidateEditAsync(CatalogueViewModel model) IsValid = !details.Any(), }; } + /// + /// The UpdateCatalogueOwnerAsync. + /// + /// The userId. + /// The catalogue owner. + /// The catalogue view model. + public async Task UpdateCatalogueOwnerAsync(int userId, CatalogueOwnerViewModel catalogueOwner) + { + await this.catalogueNodeVersionRepository.UpdateCatalogueOwnerAsync(userId, catalogueOwner); + + return new LearningHubValidationResult(true); + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 55b812fdb..356865974 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -374,5 +374,25 @@ public async Task AcceptAccessRequest(int accessRequestId) return this.Ok(await this.catalogueService.AcceptAccessAsync(this.CurrentUserId.GetValueOrDefault(), accessRequestId)); } + /// + /// The UpdateCatalogueOwner. + /// + /// The catalogue owner. + /// The updated catalogue owner. + [HttpPut] + [Route("UpdateCatalogueOwner")] + public async Task UpdateCatalogueOwner(CatalogueOwnerViewModel viewModel) + { + try + { + var vr = await this.catalogueService.UpdateCatalogueOwnerAsync(this.CurrentUserId.GetValueOrDefault(), viewModel); + return this.Ok(new ApiResponse(true, vr)); + } + catch (Exception ex) + { + return this.Ok(new ApiResponse(false, new LearningHubValidationResult(false, ex.Message))); + } + } + } } From 01872637c72092db8ca82504e4325798a5314c5d Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 8 Jul 2025 11:17:39 +0100 Subject: [PATCH 64/83] Merge RC into 5490 Branch --- .../Services/UserGroupService.cs | 29 ++++++++++--------- .../LearningHub.Nhs.OpenApi.Tests.csproj | 1 + .../Services/Services/ResourceServiceTests.cs | 4 +-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs index e5c77a8ce..98902ca7d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserGroupService.cs @@ -49,11 +49,6 @@ public class UserGroupService : IUserGroupService ///
private IScopeRepository scopeRepository; - /// - /// The role user group repository. - /// - private IRoleUserGroupRepository roleUserGroupRepository; - /// /// The user group attribute repository. /// @@ -63,12 +58,10 @@ public class UserGroupService : IUserGroupService /// Initializes a new instance of the class. ///
/// roleUserGroupRepository. - public UserGroupService(IRoleUserGroupRepository roleUserGroupRepository) /// The catalogue service. /// The user group repository. /// The user - user group repository. /// The scope repository. - /// The role - user group repository. /// The user group attribute repository. /// The mapper. public UserGroupService( @@ -110,6 +103,18 @@ public async Task GetByIdAsync(int id, bool includeRoles) return await userGroupRepository.GetByIdAsync(id, includeRoles); } + /// + public async Task UserHasCatalogueContributionPermission(int userId) + { + var userRoleGroups = await this.roleUserGroupRepository.GetRoleUserGroupViewModelsByUserId(userId); + if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.LocalAdmin || r.RoleEnum == RoleEnum.Editor)) + { + return true; + } + + return false; + } + /// /// The create async. /// @@ -927,18 +932,16 @@ private IQueryable OrderUserGroupItems(IQueryable items, s break; default: if (sortDirection == "D") - { - var userRoleGroups = await this.roleUserGroupRepository.GetRoleUserGroupViewModelsByUserId(userId); - if (userRoleGroups != null && userRoleGroups.Any(r => r.RoleEnum == RoleEnum.LocalAdmin || r.RoleEnum == RoleEnum.Editor)) + { items = items.OrderByDescending(x => x.Id); } else - { + { items = items.OrderBy(x => x.Id); - } + } break; - } + } return items; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 3efc63b33..c16540ee6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -25,6 +25,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs index e9eba00aa..ce60ea029 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs @@ -6,7 +6,6 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using System.Net; using System.Threading.Tasks; using AutoMapper; - using FizzWare.NBuilder; using FluentAssertions; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; @@ -24,9 +23,8 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using LearningHub.Nhs.OpenApi.Tests.TestHelpers; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; - using Moq; + using Moq; using Xunit; public class ResourceServiceTests From 993b3c731ec67736bc1daf704686c55849f872f7 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 8 Jul 2025 16:11:42 +0100 Subject: [PATCH 65/83] TD-5500: Invalid date time in my learning page --- .../Services/MyLearningService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index 343626909..a6d2f1b06 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -246,7 +246,7 @@ public async Task> PopulateMyLearningDetai MinorVersion = resourceActivity.MinorVersion, Version = resourceActivity.MajorVersion + "." + resourceActivity.MinorVersion, ResourceType = resourceActivity.Resource.ResourceTypeEnum, - ActivityDate = resourceActivity.ActivityStart.GetValueOrDefault(), + ActivityDate = resourceActivity.ActivityStart ?? resourceActivity.CreateDate, ActivityStatus = (ActivityStatusEnum)resourceActivity.ActivityStatusId, IsCurrentResourceVersion = resourceActivity.ResourceVersionId == resourceActivity.Resource.CurrentResourceVersionId, VersionStatusId = (int?)resourceActivity.ResourceVersion.VersionStatusEnum, From 6489346725b6f6cbf461689af47e658290eb0ca7 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 14 Jul 2025 16:30:54 +0100 Subject: [PATCH 66/83] TD-5752: Issue showing 'Your password has been changed' when logged out from the moodle screen --- LearningHub.Nhs.WebUI/Controllers/HomeController.cs | 2 +- LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 6d578a339..d5f53c00d 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -378,7 +378,7 @@ public IActionResult UserLogout() [AllowAnonymous] public IActionResult Logout() { - var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=true"; + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isPasswordUpdate=false"; return this.Redirect(redirectUri); } diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index 6fd57f208..d1dadb902 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -458,7 +458,7 @@ public async Task UpdatePassword(ChangePasswordViewModel model) if (this.ModelState.IsValid) { await this.userService.UpdatePassword(model.NewPassword); - var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=false"; + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isPasswordUpdate=true"; return this.Redirect(redirectUri); } else From 4ae759a60156d19f84e3d135079f4bf71d30a612 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Tue, 22 Jul 2025 09:35:27 +0100 Subject: [PATCH 67/83] Revert "Merge pull request #1258 from TechnologyEnhancedLearning/Develop/Fixes/TD-4916-nhsuk-override-consolidation" This reverts commit 3dbcd20d1d9b06378c618c51e44fb4049001a6a4, reversing changes made to a383b7290eedf63d794acb3fd22523e9b55c811d. --- .../Styles/nhsuk/layout.scss | 439 ++++++++++++++++ LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss | 486 +----------------- 2 files changed, 440 insertions(+), 485 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index f8e150eeb..4cb2822fe 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -5,11 +5,126 @@ body { overflow-wrap: break-word; } +.nhsuk-header { + padding: 0 px2rem(32); +} + +.nhsuk-width-container.app-width-container { + max-width: px2rem(1208); + margin: 0 auto; + padding-left: px2rem(32); + padding-right: px2rem(32); +} + +.nhsuk-header .nhsuk-width-container.app-width-container { + max-width: px2rem(1144); + margin: 0 auto; +} + +.nhsuk-width-container.app-width-container.beta-banner { + padding: px2rem(8) px2rem(32); + max-width: px2rem(1208); + margin: 0 auto; +} + +.nhsuk-header .nhsuk-header__container::after { + content: none; +} + +.nhsuk-header__navigation.app-width-container { + max-width: px2rem(1144); +} + +.app-width-container--full { + margin: 0; + max-width: none +} .app-main-wrapper--no-padding { padding: 0 } +.nhsuk-header__container.app-width-container { + display: flex; + justify-content: space-between; + gap: 0 px2rem(24); + padding: px2rem(16) 0; +} + +.nhsuk-header__content { + display: flex; + align-items: center; + min-height: px2rem(40); + margin-left: auto; +} + +.nhsuk-header__logo { + flex: 1 0 0; +} + +.nhsuk-header__logo .nhsuk-header__link--service { + display: inline-flex; +} + +.nhsuk-header__service-name { + font-size: px2rem(19); +} + +.nhsuk-account__login { + font-size: px2rem(14); + float: right; + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + gap: px2rem(24); +} + +.nhsuk-header__notification-dot { + position: absolute; + top: px2rem(8); + right: px2rem(-10); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: white; + min-width: px2rem(18); + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + padding: px2rem(1) px2rem(3) 0; +} + +.nhsuk-header__menu { + display: none; +} + +.nhsuk-header__search .nhsuk-search__input { + width: px2rem(260); +} + +.nhsuk-header__search { + .nhsuk-search__input { + width: px2rem(260); + + &::-moz-placeholder { + opacity: 1; + } + } + + #search > label.nhsuk-u-visually-hidden { + background-color: $nhsuk-white; + } +} + +.nhsuk-account__login--link, +.nhsuk-account__login--link:visited, +.nhsuk-account__login--link:hover { + color: #fff; +} + .beta-banner { background-color: $color_nhsuk-grey-5; color: $nhsuk-text-color; @@ -40,6 +155,10 @@ body { font-size: px2rem(16); } +.nhsuk-footer { + padding: px2rem(48) 0; +} + #header-dropdown-menu-control { opacity: 0; @@ -54,6 +173,23 @@ body { display: none; } +.nhsuk-header__break { + display: none; +} + +.nhsuk-header__mobile-only-nav { + display: none; +} + +.nhsuk-header__mobile-break { + display: none; +} + +.nhsuk-header__navigation-item--current { + a { + font-weight: bold; + } +} button[data-toggle="modal"] { color: #005eb8; @@ -118,9 +254,138 @@ button[data-toggle="modal"] { li.autosuggestion-option:last-of-type { border-bottom: none !important; } +/* large desktop */ +@media (min-width: px2rem(990)) { + + .nhsuk-header__navigation-item--current a { + border-bottom: 4px solid $nhsuk-grey-lighter; + font-weight: normal; + } + + .nhsuk-header__navigation-link { + position: relative; + } + + .nhsuk-header__navigation-item:last-child { + margin-right: 16px; + } +} /* small desktop */ @media (max-width: px2rem(989)) { + + .nhsuk-header__container { + flex-wrap: wrap; + } + + .nhsuk-header { + padding: 0; + } + + .nhsuk-header__container.app-width-container { + flex-wrap: wrap; + gap: 0 0; + padding: px2rem(16) px2rem(32); + } + + .nhsuk-header__link--service { + align-items: center; + -ms-flex-align: center; + margin-bottom: 0; + width: auto; + } + + .nhsuk-header__service-name { + padding-left: px2rem(16); + } + + .nhsuk-header__logo { + order: 0; + } + + .nhsuk-account__login { + order: 1; + margin-left: auto; + margin-right: 0px; + } + + .nhsuk-header__break { + display: block; + width: 100%; + height: px2rem(24); + order: 2 + } + + .nhsuk-header__search { + order: 3; + flex-grow: 1; + margin-left: 0; + margin-right: px2rem(24); + } + + .nhsuk-header__menu { + display: block; + position: relative; + order: 4; + flex: 0 0 px2rem(74); + } + + .nhsuk-header__navigation-list .nhsuk-header__navigation-item, + .nhsuk-header__navigation .nhsuk-header__navigation-title { + border-top: 1px solid $color_nhsuk-grey-4; + } + + .nhsuk-header__menu-toggle { + text-align: center; + margin: 0; + right: 0; + font-weight: 600; + } + + .nhsuk-header__search-form { + display: flex; + } + + .nhsuk-header__search .nhsuk-search__input { + flex: 1 0 0; + } + + #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block; + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(15); + left: px2rem(115); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: $nhsuk-white; + min-width: px2rem(18); + width: fit-content; + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + } + + .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { + left: px2rem(125); + } + + .nhsuk-header__menu-notification-dot { + position: absolute; + top: px2rem(-5); + right: px2rem(-6); + background: $nhsuk-error-color; + width: px2rem(12); + height: px2rem(12); + border-radius: px2rem(6); + box-shadow: 0 0 0 2px white; + z-index: 10; + } + .autosuggestion-menu { top: 100%; } @@ -129,21 +394,192 @@ li.autosuggestion-option:last-of-type { /* tablet */ @media (max-width: px2rem(768)) { + .nhsuk-width-container.app-width-container, + .nhsuk-width-container.app-width-container.beta-banner { + padding-left: px2rem(16); + padding-right: px2rem(16); + } + + .nhsuk-back-link { + padding: 0.5rem 0; + } + + .nhsuk-header__menu .nhsuk-header__not-mobile { + display: none; + } + .autosuggestion-menu { top: 100%; } + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + #header-mobile-search-control { display: block; opacity: 0; position: absolute; } + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } } /* mobile */ @media (max-width: px2rem(640)) { + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__logo { + max-width: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { + align-items: center; + justify-content: flex-end; + padding-bottom: px2rem(16); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + #header-mobile-search-control { display: block; opacity: 0; @@ -154,6 +590,9 @@ li.autosuggestion-option:last-of-type { display: block; } + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } .autosuggestion-menu { top: 100%; diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss index 40c7e1095..7254e0901 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss @@ -1,485 +1 @@ -@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; - -// Overrides largely due to -// - Highly customised header -// - Use of full browser width hero images and full width colour bands -// - Beta banner -// -// Header customisation not needed with current frontend package -// -// - FGC 19/06/25 - - -.nhsuk-width-container.app-width-container { - max-width: px2rem(1208); - margin: 0 auto; - padding-left: px2rem(32); - padding-right: px2rem(32); -} - -// Header items - .nhsuk-header { - padding: 0 px2rem(32); - } - - .nhsuk-header .nhsuk-width-container.app-width-container { - max-width: px2rem(1144); - margin: 0 auto; - } - - .nhsuk-width-container.app-width-container.beta-banner { - padding: px2rem(8) px2rem(32); - max-width: px2rem(1208); - margin: 0 auto; - } - - .nhsuk-header .nhsuk-header__container::after { - content: none; - } - - .nhsuk-header__navigation.app-width-container { - max-width: px2rem(1144); - } - - .nhsuk-header__container.app-width-container { - display: flex; - justify-content: space-between; - gap: 0 px2rem(24); - padding: px2rem(16) 0; - } - - .nhsuk-header__logo { - flex: 1 0 0; - } - - .nhsuk-header__logo .nhsuk-header__link--service { - display: inline-flex; - } - - .nhsuk-header__service-name { - font-size: px2rem(19); - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(8); - right: px2rem(-10); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: white; - min-width: px2rem(18); - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - padding: px2rem(1) px2rem(3) 0; - } - - .nhsuk-header__menu { - display: none; - } - - .nhsuk-header__search .nhsuk-search__input { - width: px2rem(260); - } - - .nhsuk-header__search { - .nhsuk-search__input { - width: px2rem(260); - - &::-moz-placeholder { - opacity: 1; - } - } - - #search > label.nhsuk-u-visually-hidden { - background-color: $nhsuk-white; - } - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: none; - } - - .nhsuk-header__mobile-break { - display: none; - } - - .nhsuk-header__navigation-item--current { - a { - font-weight: bold; - } - } - - .nhsuk-account__login { - // also a header item - font-size: px2rem(14); - float: right; - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - align-items: center; - gap: px2rem(24); - } - - .nhsuk-account__login--link, - .nhsuk-account__login--link:visited, - .nhsuk-account__login--link:hover { - // all header items - color: #fff; - } - - -// End of header items - - -.app-width-container--full { - // used to allow placement of hero (full width) images - margin: 0; - max-width: none -} - -.app-main-wrapper--no-padding { - // allowing hero image to touch headers - padding: 0 -} - -.nhsuk-footer { - padding: px2rem(48) 0; -} - - - -/* large desktop */ -@media (min-width: px2rem(990)) { - // entirely headers - - .nhsuk-header__navigation-item--current a { - border-bottom: 4px solid $nhsuk-grey-lighter; - font-weight: normal; - } - - .nhsuk-header__navigation-link { - position: relative; - } - - .nhsuk-header__navigation-item:last-child { - margin-right: 16px; - } -} - -/* small desktop */ -@media (max-width: px2rem(989)) { - - //entirely headers - - .nhsuk-header__container { - flex-wrap: wrap; - } - - .nhsuk-header { - padding: 0; - } - - .nhsuk-header__container.app-width-container { - flex-wrap: wrap; - gap: 0 0; - padding: px2rem(16) px2rem(32); - } - - .nhsuk-header__link--service { - align-items: center; - -ms-flex-align: center; - margin-bottom: 0; - width: auto; - } - - .nhsuk-header__service-name { - padding-left: px2rem(16); - } - - .nhsuk-header__logo { - order: 0; - } - - .nhsuk-header__break { - display: block; - width: 100%; - height: px2rem(24); - order: 2 - } - - .nhsuk-header__search { - order: 3; - flex-grow: 1; - margin-left: 0; - margin-right: px2rem(24); - } - - .nhsuk-header__menu { - display: block; - position: relative; - order: 4; - flex: 0 0 px2rem(74); - } - - .nhsuk-header__navigation-list .nhsuk-header__navigation-item, - .nhsuk-header__navigation .nhsuk-header__navigation-title { - border-top: 1px solid $color_nhsuk-grey-4; - } - - .nhsuk-header__menu-toggle { - text-align: center; - margin: 0; - right: 0; - font-weight: 600; - } - - .nhsuk-header__search-form { - display: flex; - } - - .nhsuk-header__search .nhsuk-search__input { - flex: 1 0 0; - } - - #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block; - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(15); - left: px2rem(115); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: $nhsuk-white; - min-width: px2rem(18); - width: fit-content; - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - } - - .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { - left: px2rem(125); - } - - .nhsuk-header__menu-notification-dot { - position: absolute; - top: px2rem(-5); - right: px2rem(-6); - background: $nhsuk-error-color; - width: px2rem(12); - height: px2rem(12); - border-radius: px2rem(6); - box-shadow: 0 0 0 2px white; - z-index: 10; - } - - .nhsuk-account__login { - // also part of the header - order: 1; - margin-left: auto; - margin-right: 0px; - } - -} - -@media (max-width: px2rem(768)) { - .nhsuk-width-container.app-width-container, - .nhsuk-width-container.app-width-container.beta-banner { - padding-left: px2rem(16); - padding-right: px2rem(16); - } - - // entirely headers from this point - - .nhsuk-back-link { - padding: 0.5rem 0; - } - - .nhsuk-header__menu .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } - -} - -@media (max-width: px2rem(640)) { - - //entirely headers - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__logo { - max-width: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { - align-items: center; - justify-content: flex-end; - padding-bottom: px2rem(16); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } -} \ No newline at end of file +@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; \ No newline at end of file From f013da6beaec76c315f2b6f4143dd968646d1bc0 Mon Sep 17 00:00:00 2001 From: AnjuJose011 <154979799+AnjuJose011@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:54:02 +0100 Subject: [PATCH 68/83] Fixes --- .../Hierarchy/CatalogueNodeVersionRepository.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs index f533a79be..b8b7e93c7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -82,10 +82,6 @@ public async Task> GetPublishedCatalogues() /// The . public IQueryable GetPublishedCataloguesForUserAsync(int userId) { - var communityCatalogue = DbContext.CatalogueNodeVersion.AsNoTracking() - .Include(cnv => cnv.NodeVersion.Node) - .Where(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published - && cnv.NodeVersion.NodeId == 1 /* Community Catalogue */); var cataloguesForUser = from cnv in DbContext.CatalogueNodeVersion.Include(cnv => cnv.NodeVersion.Node).AsNoTracking() join nv in DbContext.NodeVersion.Where(cnv => cnv.VersionStatusEnum == VersionStatusEnum.Published && !cnv.Deleted) // .Include(nv => nv.Node) @@ -100,9 +96,9 @@ join n in DbContext.Node.Where(x => !x.Deleted) on nv.Id equals n.CurrentNodeVersionId select cnv; - var returnedCatalogues = communityCatalogue.Union(cataloguesForUser).Distinct() - .OrderBy(cnv => cnv.NodeVersion.NodeId != 1) - .ThenBy(cnv => cnv.Name); + var returnedCatalogues = cataloguesForUser.Distinct() + .OrderBy(cnv => cnv.NodeVersion.NodeId != 1) + .ThenBy(cnv => cnv.Name); return returnedCatalogues; } From 8983d3f652e4c2df79eaeefb14906bad799b7079 Mon Sep 17 00:00:00 2001 From: Frank C <115793157+frank-hee@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:46:01 +0100 Subject: [PATCH 69/83] Move .nhsuk overrides to new scss file and update layout --- .../Styles/nhsuk/common.scss | 172 +---- .../Styles/nhsuk/layout.scss | 33 - .../Styles/nhsuk/nhsuk-overrides.scss | 687 ++++++++++++++++++ LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss | 486 +------------ .../Shared/Tenant/LearningHub/_Layout.cshtml | 1 + 5 files changed, 690 insertions(+), 689 deletions(-) create mode 100644 LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/common.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/common.scss index 1c18cc3cc..e7e49e651 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/common.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/common.scss @@ -1,80 +1,6 @@ @use "../abstracts/all" as *; @use "nhsuk" as *; -.nhsuk-u-font-style-italic { - font-style: italic; -} - -.nhsuk-button--red { - background-color: $nhsuk-red !important; - - &:hover { - background-color: $nhsuk-red-hover !important; - border-color: $nhsuk-red-hover !important; - } -} - -.nhsuk-button--beta-login { - background-color: $nhsuk-blue; - box-shadow: 0 4px 0 #002f5c; -} - -.nhsuk-button--beta-login:hover { - background-color: $nhsuk-btn-blue-hover; -} - -// By default, the text on reverse (white) buttons turns white once clicked, rendering them invisible. -.nhsuk-button--reverse:visited { - color: $nhsuk-black -} - -.nhsuk-back-link { - padding: px2rem(20) 0; - margin-bottom: 0; -} - -.nhsuk-radios__divider { - text-align: left; - width: unset; -} - -.nhsuk-radios__item label { - font-family: $font-stack; -} - -/* Conditional radio buttons - Note: The nhsuk-radios__conditional element needs to be a SIBLING of the radio button input element - otherwise the CSS selector won't work. See Views/Bookmark/Move.cshtml for a usage example. - The NHSUK component (nhsuk-radios__conditional) requires JavaScript to work. These tweaks allow it to work without. -*/ -.nhsuk-radios__conditional { - display: none; - margin-left: -22px; - margin-top: 8px; -} - -.nhsuk-radios__input:checked ~ .nhsuk-radios__conditional { - display: block !important; -} - -/* jquery unbobtrusive validation style over */ -.nhsuk-error-summary__list li { - color: $nhsuk-red; -} - -.nhsuk-form-group.input-validation-error { - @extend .nhsuk-form-group--error; -} - -.nhsuk-input.input-validation-error { - @extend .nhsuk-input--error -} - -.nhsuk-input:focus { - border: 2px solid #212b32; - box-shadow: inset 0 0 0 2px; - outline: 4px solid #ffeb3b; /* 1 */ - outline-offset: 0; -} .display--hide { display: none !important; @@ -120,92 +46,6 @@ } -.nhsuk-bg-light-blue { - background-color: $nhsuk-light-blue-color; -} - -.nhsuk-bg-pale-blue { - background-color: $nhsuk-pale-blue-color; -} - -.nhsuk-bg-white { - background-color: $color_nhsuk-white; -} - -.nhsuk-width-container.search-width-container { - max-width: px2rem(752); - margin: 0 auto; - padding-left: px2rem(68); - padding-right: px2rem(68); -} - -#maincontent { - - button[class^='nhsuk-search__submit'] span.nhsuk-u-visually-hidden { - color: $color_nhsuk-grey-1; - background-color: $color_nhsuk-white; - } -} - -form label.nhsuk-u-visually-hidden { - color: $color_nhsuk-grey-1; - background-color: $color_nhsuk-white; -} - -/* One third column layout that switches to full width at the small desktop breakpoint (990px) instead of mobile. */ -.nhsuk-grid-column-one-third-small-desktop { - @extend .nhsuk-grid-column-one-third; - width: 33.3333333333% !important; - - @media (max-width: px2rem(990)) { - width: 100% !important; - } -} - - -/* Tweaks to styling for single card view. */ -@media(min-width: 768px) and (max-width: 990px) { - .nhsuk-card-group .nhsuk-grid-column-one-third-small-desktop { - max-width: 600px !important; - } - - .nhsuk-card-group--centred { - justify-content: center !important; - } -} - -.nhsuk-card-banner-container { - padding-bottom: 42.86%; - position: relative; -} - -.nhsuk-card-banner { - height: 100%; - object-fit: cover; - object-position: left; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.nhsuk-card-banner-empty { - background-color: $nhsuk-pale-blue-color; - border-bottom: 1px solid #d8dde0; -} - -.nhsuk-error-message.error-message--margin-bottom-1 { - margin-bottom: nhsuk-spacing(1); -} - -.nhsuk-u-margin-bottom-2point5 { - margin-bottom: 12px; - - @media(max-width: 641px) { - margin-bottom: 10px; - } -} .word-break__break-word { word-break: break-word; @@ -216,9 +56,7 @@ form label.nhsuk-u-visually-hidden { justify-content: space-between; } -.nhsuk-button--no-shrink { - white-space: nowrap; -} + .modal-footer--buttons { justify-content: space-between !important; @@ -233,14 +71,6 @@ form label.nhsuk-u-visually-hidden { box-shadow: 0 0 0 0.2rem $nhsuk-yellow !important; background-color: $govuk-focus-highlight-yellow; } -/*Add a background color to the radio button when focused */ -.nhsuk-radios__input:focus + .radioButton { - box-shadow: 0 0 0 3px $nhsuk-yellow; -} -/*Add a background color to the radio button when focused */ -.nhsuk-checkboxes__input:focus + .checkmark { - box-shadow: 0 0 0 4px $nhsuk-yellow; -} .accessible-link:focus { outline: none; diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index f8e150eeb..c839722fd 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -40,21 +40,6 @@ body { font-size: px2rem(16); } - -#header-dropdown-menu-control { - opacity: 0; - position: absolute; -} - -#header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block -} - -#header-mobile-search-control { - display: none; -} - - button[data-toggle="modal"] { color: #005eb8; padding: 0; @@ -133,28 +118,10 @@ li.autosuggestion-option:last-of-type { top: 100%; } - #header-mobile-search-control { - display: block; - opacity: 0; - position: absolute; - } - } /* mobile */ @media (max-width: px2rem(640)) { - - #header-mobile-search-control { - display: block; - opacity: 0; - position: absolute; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .autosuggestion-menu { top: 100%; } diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss new file mode 100644 index 000000000..98dba42db --- /dev/null +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss @@ -0,0 +1,687 @@ +@use "../abstracts/all" as *; +@use "nhsuk" as *; + +.nhsuk-u-font-style-italic { + font-style: italic; +} + +.nhsuk-button--red { + background-color: $nhsuk-red !important; + + &:hover { + background-color: $nhsuk-red-hover !important; + border-color: $nhsuk-red-hover !important; + } +} + +.nhsuk-button--beta-login { + background-color: $nhsuk-blue; + box-shadow: 0 4px 0 #002f5c; +} + +.nhsuk-button--beta-login:hover { + background-color: $nhsuk-btn-blue-hover; +} + +// By default, the text on reverse (white) buttons turns white once clicked, rendering them invisible. +.nhsuk-button--reverse:visited { + color: $nhsuk-black +} + +.nhsuk-back-link { + padding: px2rem(20) 0; + margin-bottom: 0; +} + +.nhsuk-radios__divider { + text-align: left; + width: unset; +} + +.nhsuk-radios__item label { + font-family: $font-stack; +} + +/* Conditional radio buttons - Note: The nhsuk-radios__conditional element needs to be a SIBLING of the radio button input element + otherwise the CSS selector won't work. See Views/Bookmark/Move.cshtml for a usage example. + The NHSUK component (nhsuk-radios__conditional) requires JavaScript to work. These tweaks allow it to work without. +*/ +.nhsuk-radios__conditional { + display: none; + margin-left: -22px; + margin-top: 8px; +} + +.nhsuk-radios__input:checked ~ .nhsuk-radios__conditional { + display: block !important; +} + +/* jquery unbobtrusive validation style over */ +.nhsuk-error-summary__list li { + color: $nhsuk-red; +} + +.nhsuk-form-group.input-validation-error { + @extend .nhsuk-form-group--error; +} + +.nhsuk-input.input-validation-error { + @extend .nhsuk-input--error +} + +.nhsuk-input:focus { + border: 2px solid #212b32; + box-shadow: inset 0 0 0 2px; + outline: 4px solid #ffeb3b; /* 1 */ + outline-offset: 0; +} + +.nhsuk-bg-light-blue { + background-color: $nhsuk-light-blue-color; +} + +.nhsuk-bg-pale-blue { + background-color: $nhsuk-pale-blue-color; +} + +.nhsuk-bg-white { + background-color: $color_nhsuk-white; +} + +.nhsuk-width-container.search-width-container { + max-width: px2rem(752); + margin: 0 auto; + padding-left: px2rem(68); + padding-right: px2rem(68); +} + +#maincontent { + button[class^='nhsuk-search__submit'] span.nhsuk-u-visually-hidden { + color: $color_nhsuk-grey-1; + background-color: $color_nhsuk-white; + } +} + +form label.nhsuk-u-visually-hidden { + color: $color_nhsuk-grey-1; + background-color: $color_nhsuk-white; +} + +/* One third column layout that switches to full width at the small desktop breakpoint (990px) instead of mobile. */ +.nhsuk-grid-column-one-third-small-desktop { + @extend .nhsuk-grid-column-one-third; + width: 33.3333333333% !important; + + @media (max-width: px2rem(990)) { + width: 100% !important; + } +} + + +/* Tweaks to styling for single card view. */ +@media(min-width: 768px) and (max-width: 990px) { + .nhsuk-card-group .nhsuk-grid-column-one-third-small-desktop { + max-width: 600px !important; + } + + .nhsuk-card-group--centred { + justify-content: center !important; + } +} + +.nhsuk-card-banner-container { + padding-bottom: 42.86%; + position: relative; +} + +.nhsuk-card-banner { + height: 100%; + object-fit: cover; + object-position: left; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.nhsuk-card-banner-empty { + background-color: $nhsuk-pale-blue-color; + border-bottom: 1px solid #d8dde0; +} + +.nhsuk-error-message.error-message--margin-bottom-1 { + margin-bottom: nhsuk-spacing(1); +} + +.nhsuk-u-margin-bottom-2point5 { + margin-bottom: 12px; + + @media(max-width: 641px) { + margin-bottom: 10px; + } +} + +.nhsuk-button--no-shrink { + white-space: nowrap; +} + +/*Add a background color to the radio button when focused */ +.nhsuk-radios__input:focus + .radioButton { + box-shadow: 0 0 0 3px $nhsuk-yellow; +} +/*Add a background color to the radio button when focused */ +.nhsuk-checkboxes__input:focus + .checkmark { + box-shadow: 0 0 0 4px $nhsuk-yellow; +} + + + +// Below taken from layout.scss +// Overrides largely due to +// - Highly customised header +// - Use of full browser width hero images and full width colour bands +// - Beta banner +// +// Header customisation not needed with current frontend package +// - FGC 19/06/25 + + +.nhsuk-width-container.app-width-container { + max-width: px2rem(1208); + margin: 0 auto; + padding-left: px2rem(32); + padding-right: px2rem(32); +} + +// Header items + .nhsuk-header { + padding: 0 px2rem(32); + } + + .nhsuk-header .nhsuk-width-container.app-width-container { + max-width: px2rem(1144); + margin: 0 auto; + } + + .nhsuk-width-container.app-width-container.beta-banner { + padding: px2rem(8) px2rem(32); + max-width: px2rem(1208); + margin: 0 auto; + } + + .nhsuk-header .nhsuk-header__container::after { + content: none; + } + + .nhsuk-header__navigation.app-width-container { + max-width: px2rem(1144); + } + + .nhsuk-header__container.app-width-container { + display: flex; + justify-content: space-between; + gap: 0 px2rem(24); + padding: px2rem(16) 0; + } + + .nhsuk-header__logo { + flex: 1 0 0; + } + + .nhsuk-header__logo .nhsuk-header__link--service { + display: inline-flex; + } + + .nhsuk-header__service-name { + font-size: px2rem(19); + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(8); + right: px2rem(-10); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: white; + min-width: px2rem(18); + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + padding: px2rem(1) px2rem(3) 0; + } + + .nhsuk-header__menu { + display: none; + } + + .nhsuk-header__search .nhsuk-search__input { + width: px2rem(260); + } + + .nhsuk-header__search { + .nhsuk-search__input { + width: px2rem(260); + + &::-moz-placeholder { + opacity: 1; + } + } + + #search > label.nhsuk-u-visually-hidden { + background-color: $nhsuk-white; + } + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-only-nav { + display: none; + } + + .nhsuk-header__mobile-break { + display: none; + } + + .nhsuk-header__navigation-item--current { + a { + font-weight: bold; + } + } + + .nhsuk-account__login { + // also a header item + font-size: px2rem(14); + float: right; + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + gap: px2rem(24); + } + + .nhsuk-account__login--link, + .nhsuk-account__login--link:visited, + .nhsuk-account__login--link:hover { + // all header items + color: #fff; + } + + +// End of header items + + +.app-width-container--full { + // used to allow placement of hero (full width) images + margin: 0; + max-width: none +} + +.app-main-wrapper--no-padding { + // allowing hero image to touch headers + padding: 0 +} + +.nhsuk-footer { + padding: px2rem(48) 0; +} + + + +/* large desktop */ +@media (min-width: px2rem(990)) { + // entirely headers + + .nhsuk-header__navigation-item--current a { + border-bottom: 4px solid $nhsuk-grey-lighter; + font-weight: normal; + } + + .nhsuk-header__navigation-link { + position: relative; + } + + .nhsuk-header__navigation-item:last-child { + margin-right: 16px; + } +} + +/* small desktop */ +@media (max-width: px2rem(989)) { + + //entirely headers + + .nhsuk-header__container { + flex-wrap: wrap; + } + + .nhsuk-header { + padding: 0; + } + + .nhsuk-header__container.app-width-container { + flex-wrap: wrap; + gap: 0 0; + padding: px2rem(16) px2rem(32); + } + + .nhsuk-header__link--service { + align-items: center; + -ms-flex-align: center; + margin-bottom: 0; + width: auto; + } + + .nhsuk-header__service-name { + padding-left: px2rem(16); + } + + .nhsuk-header__logo { + order: 0; + } + + .nhsuk-header__break { + display: block; + width: 100%; + height: px2rem(24); + order: 2 + } + + .nhsuk-header__search { + order: 3; + flex-grow: 1; + margin-left: 0; + margin-right: px2rem(24); + } + + .nhsuk-header__menu { + display: block; + position: relative; + order: 4; + flex: 0 0 px2rem(74); + } + + .nhsuk-header__navigation-list .nhsuk-header__navigation-item, + .nhsuk-header__navigation .nhsuk-header__navigation-title { + border-top: 1px solid $color_nhsuk-grey-4; + } + + .nhsuk-header__menu-toggle { + text-align: center; + margin: 0; + right: 0; + font-weight: 600; + } + + .nhsuk-header__search-form { + display: flex; + } + + .nhsuk-header__search .nhsuk-search__input { + flex: 1 0 0; + } + + #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block; + } + + .nhsuk-header__notification-dot { + position: absolute; + top: px2rem(15); + left: px2rem(115); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: $nhsuk-white; + min-width: px2rem(18); + width: fit-content; + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + } + + .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { + left: px2rem(125); + } + + .nhsuk-header__menu-notification-dot { + position: absolute; + top: px2rem(-5); + right: px2rem(-6); + background: $nhsuk-error-color; + width: px2rem(12); + height: px2rem(12); + border-radius: px2rem(6); + box-shadow: 0 0 0 2px white; + z-index: 10; + } + + .nhsuk-account__login { + // also part of the header + order: 1; + margin-left: auto; + margin-right: 0px; + } + +} + +@media (max-width: px2rem(768)) { + .nhsuk-width-container.app-width-container, + .nhsuk-width-container.app-width-container.beta-banner { + padding-left: px2rem(16); + padding-right: px2rem(16); + } + + // entirely headers from this point + + .nhsuk-back-link { + padding: 0.5rem 0; + } + + .nhsuk-header__menu .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + #header-dropdown-menu-control { + opacity: 0; + position: absolute; + } + + #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block + } + + #header-mobile-search-control { + display: none; + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + + #header-mobile-search-control { + display: block; + opacity: 0; + position: absolute; + } + + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } + +} + +@media (max-width: px2rem(640)) { + + //entirely headers + + .nhsuk-header__not-mobile { + display: none; + } + + .nhsuk-header__logo { + max-width: none; + } + + .nhsuk-header__mobile-only-nav { + display: flex; + order: 1; + justify-content: space-around; + gap: 0 px2rem(16); + align-items: flex-start; + flex-wrap: wrap; + width: px2rem(166); + } + + .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { + align-items: center; + justify-content: flex-end; + padding-bottom: px2rem(16); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__menu { + margin-right: px2rem(12); + } + + .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { + margin-left: px2rem(12); + } + + .nhsuk-header__break { + display: none; + } + + .nhsuk-header__mobile-break { + display: block; + width: 100%; + height: 0; + } + + .nhsuk-header__link--service { + flex-direction: column; + align-items: flex-start; + } + + .nhsuk-header__notification-dot { + top: px2rem(13); + left: px2rem(100); + } + + .nhsuk-header__service-name { + padding: px2rem(12) 0 0; + } + + .nhsuk-header__search-toggle { + position: relative; + height: px2rem(40); + order: 2; + padding: px2rem(7) px2rem(10) 0; + margin: 0 + } + + .nhsuk-header__search .nhsuk-search__submit { + padding-top: nhsuk-spacing(1); + } + + .nhsuk-header__menu { + order: 3; + } + + .nhsuk-header__search { + order: 4; + width: 100%; + flex-grow: 1; + margin: px2rem(16) px2rem(-16) 0; + border-bottom: 1px solid $color_nhsuk-grey-4; + } + + #header-mobile-search-control { + display: block; + opacity: 0; + position: absolute; + } + + #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { + display: block; + } + + .nhsuk-width-container.nhsuk-header__container.app-width-container { + padding-bottom: 0; + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss index 40c7e1095..7254e0901 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk.scss @@ -1,485 +1 @@ -@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; - -// Overrides largely due to -// - Highly customised header -// - Use of full browser width hero images and full width colour bands -// - Beta banner -// -// Header customisation not needed with current frontend package -// -// - FGC 19/06/25 - - -.nhsuk-width-container.app-width-container { - max-width: px2rem(1208); - margin: 0 auto; - padding-left: px2rem(32); - padding-right: px2rem(32); -} - -// Header items - .nhsuk-header { - padding: 0 px2rem(32); - } - - .nhsuk-header .nhsuk-width-container.app-width-container { - max-width: px2rem(1144); - margin: 0 auto; - } - - .nhsuk-width-container.app-width-container.beta-banner { - padding: px2rem(8) px2rem(32); - max-width: px2rem(1208); - margin: 0 auto; - } - - .nhsuk-header .nhsuk-header__container::after { - content: none; - } - - .nhsuk-header__navigation.app-width-container { - max-width: px2rem(1144); - } - - .nhsuk-header__container.app-width-container { - display: flex; - justify-content: space-between; - gap: 0 px2rem(24); - padding: px2rem(16) 0; - } - - .nhsuk-header__logo { - flex: 1 0 0; - } - - .nhsuk-header__logo .nhsuk-header__link--service { - display: inline-flex; - } - - .nhsuk-header__service-name { - font-size: px2rem(19); - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(8); - right: px2rem(-10); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: white; - min-width: px2rem(18); - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - padding: px2rem(1) px2rem(3) 0; - } - - .nhsuk-header__menu { - display: none; - } - - .nhsuk-header__search .nhsuk-search__input { - width: px2rem(260); - } - - .nhsuk-header__search { - .nhsuk-search__input { - width: px2rem(260); - - &::-moz-placeholder { - opacity: 1; - } - } - - #search > label.nhsuk-u-visually-hidden { - background-color: $nhsuk-white; - } - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: none; - } - - .nhsuk-header__mobile-break { - display: none; - } - - .nhsuk-header__navigation-item--current { - a { - font-weight: bold; - } - } - - .nhsuk-account__login { - // also a header item - font-size: px2rem(14); - float: right; - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - align-items: center; - gap: px2rem(24); - } - - .nhsuk-account__login--link, - .nhsuk-account__login--link:visited, - .nhsuk-account__login--link:hover { - // all header items - color: #fff; - } - - -// End of header items - - -.app-width-container--full { - // used to allow placement of hero (full width) images - margin: 0; - max-width: none -} - -.app-main-wrapper--no-padding { - // allowing hero image to touch headers - padding: 0 -} - -.nhsuk-footer { - padding: px2rem(48) 0; -} - - - -/* large desktop */ -@media (min-width: px2rem(990)) { - // entirely headers - - .nhsuk-header__navigation-item--current a { - border-bottom: 4px solid $nhsuk-grey-lighter; - font-weight: normal; - } - - .nhsuk-header__navigation-link { - position: relative; - } - - .nhsuk-header__navigation-item:last-child { - margin-right: 16px; - } -} - -/* small desktop */ -@media (max-width: px2rem(989)) { - - //entirely headers - - .nhsuk-header__container { - flex-wrap: wrap; - } - - .nhsuk-header { - padding: 0; - } - - .nhsuk-header__container.app-width-container { - flex-wrap: wrap; - gap: 0 0; - padding: px2rem(16) px2rem(32); - } - - .nhsuk-header__link--service { - align-items: center; - -ms-flex-align: center; - margin-bottom: 0; - width: auto; - } - - .nhsuk-header__service-name { - padding-left: px2rem(16); - } - - .nhsuk-header__logo { - order: 0; - } - - .nhsuk-header__break { - display: block; - width: 100%; - height: px2rem(24); - order: 2 - } - - .nhsuk-header__search { - order: 3; - flex-grow: 1; - margin-left: 0; - margin-right: px2rem(24); - } - - .nhsuk-header__menu { - display: block; - position: relative; - order: 4; - flex: 0 0 px2rem(74); - } - - .nhsuk-header__navigation-list .nhsuk-header__navigation-item, - .nhsuk-header__navigation .nhsuk-header__navigation-title { - border-top: 1px solid $color_nhsuk-grey-4; - } - - .nhsuk-header__menu-toggle { - text-align: center; - margin: 0; - right: 0; - font-weight: 600; - } - - .nhsuk-header__search-form { - display: flex; - } - - .nhsuk-header__search .nhsuk-search__input { - flex: 1 0 0; - } - - #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block; - } - - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(15); - left: px2rem(115); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: $nhsuk-white; - min-width: px2rem(18); - width: fit-content; - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - } - - .nhsuk-header__navigation-item--current .nhsuk-header__notification-dot { - left: px2rem(125); - } - - .nhsuk-header__menu-notification-dot { - position: absolute; - top: px2rem(-5); - right: px2rem(-6); - background: $nhsuk-error-color; - width: px2rem(12); - height: px2rem(12); - border-radius: px2rem(6); - box-shadow: 0 0 0 2px white; - z-index: 10; - } - - .nhsuk-account__login { - // also part of the header - order: 1; - margin-left: auto; - margin-right: 0px; - } - -} - -@media (max-width: px2rem(768)) { - .nhsuk-width-container.app-width-container, - .nhsuk-width-container.app-width-container.beta-banner { - padding-left: px2rem(16); - padding-right: px2rem(16); - } - - // entirely headers from this point - - .nhsuk-back-link { - padding: 0.5rem 0; - } - - .nhsuk-header__menu .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } - -} - -@media (max-width: px2rem(640)) { - - //entirely headers - - .nhsuk-header__not-mobile { - display: none; - } - - .nhsuk-header__logo { - max-width: none; - } - - .nhsuk-header__mobile-only-nav { - display: flex; - order: 1; - justify-content: space-around; - gap: 0 px2rem(16); - align-items: flex-start; - flex-wrap: wrap; - width: px2rem(166); - } - - .nhsuk-header__pre-login .nhsuk-header__mobile-only-nav { - align-items: center; - justify-content: flex-end; - padding-bottom: px2rem(16); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__menu { - margin-right: px2rem(12); - } - - .nhsuk-header__mobile-only-nav .nhsuk-header__search-toggle { - margin-left: px2rem(12); - } - - .nhsuk-header__break { - display: none; - } - - .nhsuk-header__mobile-break { - display: block; - width: 100%; - height: 0; - } - - .nhsuk-header__link--service { - flex-direction: column; - align-items: flex-start; - } - - .nhsuk-header__notification-dot { - top: px2rem(13); - left: px2rem(100); - } - - .nhsuk-header__service-name { - padding: px2rem(12) 0 0; - } - - .nhsuk-header__search-toggle { - position: relative; - height: px2rem(40); - order: 2; - padding: px2rem(7) px2rem(10) 0; - margin: 0 - } - - .nhsuk-header__search .nhsuk-search__submit { - padding-top: nhsuk-spacing(1); - } - - .nhsuk-header__menu { - order: 3; - } - - .nhsuk-header__search { - order: 4; - width: 100%; - flex-grow: 1; - margin: px2rem(16) px2rem(-16) 0; - border-bottom: 1px solid $color_nhsuk-grey-4; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } -} \ No newline at end of file +@import "../../node_modules/nhsuk-frontend/packages/nhsuk"; \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml index c16fa837c..6b679b67e 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml @@ -21,6 +21,7 @@ +
[Route("Catalogue")] [Authorize] + [ApiController] public class CatalogueController : OpenApiControllerBase { private readonly ICatalogueService catalogueService; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index 98ae2417c..343906d96 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -29,6 +29,7 @@ namespace LearningHub.NHS.OpenAPI.Controllers ///
[Route("Resource")] [Authorize] + [ApiController] public class ResourceController : OpenApiControllerBase { private const int MaxNumberOfReferenceIds = 1000; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs index b8a0b5aa2..2b463125b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/SearchController.cs @@ -11,6 +11,7 @@ using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -18,6 +19,7 @@ /// /// Search operations. /// + [Authorize] [Route("Search")] [ApiController] public class SearchController : OpenApiControllerBase diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index e5f9c180e..dfe34b2b8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -8,10 +8,12 @@ namespace LearningHub.NHS.OpenAPI using System.Collections.Generic; using System.IO; using AspNetCore.Authentication.ApiKey; + using LearningHub.Nhs.Api.Authentication; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Extensions; using LearningHub.NHS.OpenAPI.Auth; + using LearningHub.NHS.OpenAPI.Authentication; using LearningHub.NHS.OpenAPI.Configuration; using LearningHub.NHS.OpenAPI.Middleware; using LearningHub.Nhs.OpenApi.Repositories; @@ -19,9 +21,9 @@ namespace LearningHub.NHS.OpenAPI using LearningHub.Nhs.OpenApi.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -29,9 +31,6 @@ namespace LearningHub.NHS.OpenAPI using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; - using Microsoft.AspNetCore.Authorization; - using LearningHub.NHS.OpenAPI.Authentication; - using LearningHub.Nhs.Api.Authentication; /// /// The Startup class. @@ -62,8 +61,12 @@ public void ConfigureServices(IServiceCollection services) services.AddApiKeyAuth(); - services.AddAuthentication() - .AddJwtBearer(options => + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => { options.Authority = this.Configuration.GetValue("LearningHUbAuthServiceConfig:Authority"); options.TokenValidationParameters = new TokenValidationParameters() @@ -76,7 +79,7 @@ public void ConfigureServices(IServiceCollection services) }); services.AddCustomMiddleware(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddRepositories(this.Configuration); @@ -89,7 +92,6 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(options => { options.Filters.Add(new HttpResponseExceptionFilter()); - options.Filters.Add(new AuthorizeFilter()); }); services.AddMvc() From ab438ca7fa9f0fffde80eeb5c0a082daa86945d8 Mon Sep 17 00:00:00 2001 From: OluwatobiAwe Date: Wed, 13 Aug 2025 16:28:48 +0100 Subject: [PATCH 81/83] TD-5929 Added missing appsettings variable --- OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 401183bf6..7142f4b36 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -78,6 +78,7 @@ "UseRedisCache": true, "ResourcePublishQueueRouteName": "", "HierarchyEditPublishQueueName": "", + "ContentManagementQueueName": "", "AuthClientIdentityKey": "", "LHClientIdentityKey": "", "ReportApiClientIdentityKey": "", From 351f55a3c70b4af3b5d0eab6aca603dea72fad2e Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 18 Aug 2025 12:34:50 +0100 Subject: [PATCH 82/83] Fixed the routing issues for the unauthorized user access --- .../LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs | 2 +- .../LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs | 2 +- .../LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs index 15926b75e..73d406e27 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CatalogueController.cs @@ -13,7 +13,7 @@ /// Catalogue controller. /// [Route("Catalogue")] - [Authorize] + [Authorize(Policy = "AuthorizeOrCallFromLH")] [ApiController] public class CatalogueController : OpenApiControllerBase { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs index 7e42357d7..549f1f80d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs @@ -17,7 +17,7 @@ ///
[Route("Hierarchy")] [ApiController] - [Authorize] + [Authorize(Policy = "AuthorizeOrCallFromLH")] public class HierarchyController : OpenApiControllerBase { private readonly IHierarchyService hierarchyService; diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index 343906d96..b3a685de3 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -28,7 +28,7 @@ namespace LearningHub.NHS.OpenAPI.Controllers /// Resource controller. ///
[Route("Resource")] - [Authorize] + [Authorize(Policy = "AuthorizeOrCallFromLH")] [ApiController] public class ResourceController : OpenApiControllerBase { From 8b0487f9602cac7e17cc65fbfa7655515c6b9d9a Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 18 Aug 2025 15:55:12 +0100 Subject: [PATCH 83/83] Styleing issues fixed --- .../Views/Account/CreateAccountValidAccountAlreadyExists.cshtml | 2 +- LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountValidAccountAlreadyExists.cshtml b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountValidAccountAlreadyExists.cshtml index a4b62e4fd..1ebfc59d4 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountValidAccountAlreadyExists.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountValidAccountAlreadyExists.cshtml @@ -25,7 +25,7 @@
+ class="nhsuk-button nhsuk-u-margin-right-3 nhsuk-u-margin-bottom-4">Learning Hub Log in OpenAthens Log in
diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml index e87879ce7..9a3a05782 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml @@ -10,7 +10,7 @@

You have to be signed in to use this resource.

+ class="nhsuk-button nhsuk-u-margin-right-3">Learning Hub Log in OpenAthens Log in