From 020de788a809d14749cd0e3fc3b58b228700a5a5 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 19 Mar 2026 09:26:22 +0000 Subject: [PATCH 01/24] MIB Integration feature changes - TD-6547 # TD-6548 # TD-6549 --- .../Controllers/BaseController.cs | 6 + .../Controllers/CatalogueController.cs | 148 +++-- .../Helpers/TreeBuilderHelper.cs | 44 ++ .../Interfaces/IMoodleBridgeApiService.cs | 19 + .../LearningHub.Nhs.AdminUI.csproj | 2 +- .../LearningHub.Nhs.AdminUI.csproj.user | 2 +- .../ServiceCollectionExtension.cs | 1 + .../Services/MoodleBridgeApiService.cs | 62 ++ .../Views/Catalogue/MoodleCategory.cshtml | 19 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 2 +- .../Configuration/MoodleBridgeApiConfig.cs | 33 + .../Controllers/AccountController.cs | 6 +- .../Controllers/Api/MyLearningController.cs | 29 +- .../Controllers/BaseController.cs | 42 ++ .../Controllers/BookmarkController.cs | 6 +- .../Controllers/CatalogueController.cs | 77 ++- .../Controllers/ContributeController.cs | 6 +- .../ContributeResourceController.cs | 4 +- .../Controllers/FileController.cs | 6 +- .../Controllers/HomeController.cs | 10 +- .../Controllers/LoginWizardController.cs | 6 +- .../Controllers/MediaController.cs | 5 +- .../Controllers/MyAccountController.cs | 7 +- .../Controllers/MyLearningController.cs | 13 +- .../Controllers/NotificationController.cs | 4 +- .../Controllers/OfflineController.cs | 6 +- .../Controllers/OpenAthensController.cs | 5 +- .../Controllers/PoliciesController.cs | 6 +- .../Controllers/ReportsController.cs | 17 +- .../Controllers/ResourceController.cs | 5 +- .../Controllers/RoadMapController.cs | 7 +- .../Controllers/SearchController.cs | 4 +- .../Controllers/TrackingController.cs | 5 +- .../Controllers/UserController.cs | 6 +- .../Interfaces/ICategoryService.cs | 6 +- .../Interfaces/IDashboardService.cs | 4 +- .../Interfaces/IMoodleBridgeApiHttpClient.cs | 17 + .../Interfaces/IMoodleBridgeApiService.cs | 22 + .../Interfaces/IMyLearningService.cs | 10 +- .../LearningHub.Nhs.WebUI.csproj | 2 +- .../Catalogue/CatalogueIndexViewModel.cs | 2 +- .../Services/CategoryService.cs | 20 +- .../Services/DashboardService.cs | 18 +- .../Services/MoodleBridgeApiHttpClient.cs | 41 ++ .../Services/MoodleBridgeApiService.cs | 67 ++ .../Services/MyLearningService.cs | 36 +- .../Startup/ServiceMappings.cs | 1 + .../Views/Catalogue/Courses.cshtml | 4 +- .../Views/Catalogue/Index.cshtml | 8 +- .../Configuration/MoodleBridgeConfig.cs | 24 + .../LearningHub.Nhs.OpenApi.Models.csproj | 6 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 2 +- ...earningHub.Nhs.OpenApi.Repositories.csproj | 2 +- .../CatalogueNodeVersionRepository.cs | 11 +- .../HttpClients/IMoodleBridgeHttpClient.cs | 25 + ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../Services/ICategoryService.cs | 2 +- .../Services/IDashboardService.cs | 11 +- .../Services/IMoodleBridgeApiService.cs | 82 +++ .../Services/IMyLearningService.cs | 6 +- .../Helpers/MoodleInstanceUsersHelper.cs | 47 ++ .../HttpClients/MoodleBridgeHttpClient.cs | 97 +++ .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../Services/CatalogueService.cs | 6 +- .../Services/CategoryService.cs | 4 +- .../Services/DashboardService.cs | 128 ++-- .../Services/MoodleApiService.cs | 2 +- .../Services/MoodleBridgeApiService.cs | 584 ++++++++++++++++++ .../Services/MyLearningService.cs | 237 +++---- .../Startup.cs | 2 + .../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +- .../Configuration/ConfigurationExtensions.cs | 10 +- .../Controllers/CategoryController.cs | 13 +- .../Controllers/DashboardController.cs | 19 +- .../Controllers/MoodleBridgeController.cs | 51 ++ .../Controllers/MyLearningController.cs | 6 +- .../LearningHub.NHS.OpenAPI.csproj | 2 +- .../LearningHub.Nhs.OpenApi/appsettings.json | 4 + ...ub.Nhs.ReportApi.Services.Interface.csproj | 2 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 2 +- .../LearningHub.Nhs.ReportApi.csproj | 2 +- .../LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- .../CatalogueNodeVersionCategoryCreate.sql | 36 +- .../CatalogueNodeVersionCategory.sql | 1 + ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 99 files changed, 1902 insertions(+), 440 deletions(-) create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Helpers/TreeBuilderHelper.cs create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleBridgeApiService.cs create mode 100644 AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleBridgeApiService.cs create mode 100644 LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs create mode 100644 LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiHttpClient.cs create mode 100644 LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs create mode 100644 LearningHub.Nhs.WebUI/Services/MoodleBridgeApiHttpClient.cs create mode 100644 LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleBridgeConfig.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleBridgeHttpClient.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/MoodleInstanceUsersHelper.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleBridgeHttpClient.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs index 90fd149ce..f1422eaee 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/BaseController.cs @@ -1,5 +1,6 @@ namespace LearningHub.Nhs.AdminUI.Controllers { + using LearningHub.Nhs.Models.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; @@ -51,6 +52,11 @@ protected string WebRootPath } } + /// + /// Gets the CurrentUserEmail. + /// + protected string CurrentUserEmail => this.User.Identity.GetCurrentEmail(); + /// /// The OnActionExecuting. /// diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs index 508684668..25aa6cd4c 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs @@ -1,11 +1,19 @@ -namespace LearningHub.Nhs.AdminUI.Controllers +using LearningHub.Nhs.Models.Databricks; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Net; + +namespace LearningHub.Nhs.AdminUI.Controllers { + using AngleSharp.Io; + using Azure; using LearningHub.Nhs.AdminUI.Configuration; using LearningHub.Nhs.AdminUI.Extensions; + using LearningHub.Nhs.AdminUI.Helpers; using LearningHub.Nhs.AdminUI.Interfaces; using LearningHub.Nhs.AdminUI.Models; using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Databricks; using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Paging; using LearningHub.Nhs.Models.Resource; @@ -70,6 +78,11 @@ public class CatalogueController : BaseController /// private readonly IMoodleApiService moodleApiService; + /// + /// Defines the moodleApiService. + /// + private readonly IMoodleBridgeApiService moodleBridgeApiService; + /// /// Defines the _settings. /// @@ -104,6 +117,7 @@ public class CatalogueController : BaseController /// The fileService. /// The providerService. /// The moodleApiService. + /// The moodleApiService. /// The logger. /// The options. /// The websettings. @@ -114,6 +128,7 @@ public CatalogueController( IFileService fileService, IProviderService providerService, IMoodleApiService moodleApiService, + IMoodleBridgeApiService moodleBridgeApiService, ILogger logger, IOptions options, IOptions websettings) @@ -124,6 +139,7 @@ public CatalogueController( this.fileService = fileService; this.providerService = providerService; this.moodleApiService = moodleApiService; + this.moodleBridgeApiService = moodleBridgeApiService; this.logger = logger; this.websettings = websettings; this.settings = options.Value; @@ -277,17 +293,9 @@ public async Task MoodleCategory(int id) return this.RedirectToAction("Error"); } - var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync(); + var categoriesResult = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); - vm.MoodleCategories = categories; - - // Build hierarchical select list - var selectList = BuildList(categories, parentId: null, depth: 0); - foreach (var item in selectList) - { - item.Text = WebUtility.HtmlDecode(item.Text); - } - vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text"); + vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categoriesResult); this.ViewData["CatalogueName"] = vm.Name; this.ViewData["id"] = id; @@ -727,7 +735,7 @@ public async Task AddUserGroupsToCatalogue(int catalogueNodeId, i [Route("AddCategoryToCatalogue")] public async Task AddCategoryToCatalogue(CatalogueViewModel catalogueViewModel) { - if (catalogueViewModel.SelectedCategoryId == 0) + if (string.IsNullOrEmpty(catalogueViewModel.SelectedCategoryId)) { this.ModelState.AddModelError("SelectedCategoryId", "Please select a category."); } @@ -736,17 +744,8 @@ public async Task AddCategoryToCatalogue(CatalogueViewModel catal var vr = await this.catalogueService.AddCategoryToCatalogue(vm); if (vr.Success) { - var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync(); - vm.MoodleCategories = categories; - // Build hierarchical select list - var selectList = BuildList(categories, parentId: null, depth: 0); - - foreach (var item in selectList) - { - item.Text = WebUtility.HtmlDecode(item.Text); - } - - vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text"); + var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); return this.View("MoodleCategory", vm); } else @@ -764,23 +763,16 @@ public async Task AddCategoryToCatalogue(CatalogueViewModel catal /// The . [HttpGet] [Route("RemoveCategoryFromCatalogue/{categoryId}/{catalogueNodeVersionId}")] - public async Task RemoveCategoryFromCatalogue(int categoryId, int catalogueNodeVersionId) + public async Task RemoveCategoryFromCatalogue(string categoryId, int catalogueNodeVersionId) { var vm = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId); vm.SelectedCategoryId = categoryId; var vr = await this.catalogueService.RemoveCategoryFromCatalogue(vm); if (vr.Success) { - var categories = await this.moodleApiService.GetAllMoodleCategoriesAsync(); - vm.MoodleCategories = categories; - vm.SelectedCategoryId = 0; - // Build hierarchical select list - var selectList = BuildList(categories, parentId: null, depth: 0); - foreach (var item in selectList) - { - item.Text = WebUtility.HtmlDecode(item.Text); - } - vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text"); + var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); + vm.SelectedCategoryId = null; return this.View("MoodleCategory", vm); } else @@ -1090,33 +1082,91 @@ private void ValidateCatalogueOwnerVm(CatalogueOwnerViewModel vm) } } - private List BuildList(IEnumerable allCategories, int? parentId, int depth) + public static SelectList BuildMoodleCategorySelectList(IEnumerable results) { var selectList = new List(); - // Handle both null and 0 as top-level depending on Moodle data - var children = allCategories - .Where(c => c.Parent == parentId || (parentId == null && (c.Parent == 0 || c.Parent == 0))) - .OrderBy(c => c.Name) - .ToList(); - - foreach (var child in children) + foreach (var result in results) { - // Indent with non-breaking spaces so browser keeps them - string indent = new string('\u00A0', depth * 3); + if (string.IsNullOrWhiteSpace(result.Instance)) + continue; + // Instance header selectList.Add(new SelectListItem { - Value = child.Id.ToString(), - Text = $"{indent}{child.Name}" + Value = "", + Text = result.Instance, + Disabled = true, + Selected = false }); - // Recursively add nested children - selectList.AddRange(BuildList(allCategories, child.Id, depth + 1)); + // Handle instance error + if (result.Error != null) + { + selectList.Add(new SelectListItem + { + Value = "", + Text = "\u00A0\u00A0[Category unavailable]", + Disabled = true, + Selected = false + }); + + continue; + } + + var categories = result.Data?.Categories; + if (categories == null || !categories.Any()) + continue; + + var tree = categories + .GroupBy(c => c.Parent) + .ToDictionary(g => g.Key, g => g.OrderBy(x => x.Name).ToList()); + + TreeBuilderHelper.BuildTree(selectList, tree, 0, 1, result.Instance); + } + + // Decode HTML entities + foreach (var item in selectList) + { + item.Text = WebUtility.HtmlDecode(item.Text); } - return selectList; + return new SelectList(selectList, "Value", "Text"); } + /// + /// + /// + /// + /// + /// + /// + + // Recursive tree builder with indentation + private static void BuildTree( + List selectList, + Dictionary> tree, + int parentId, + int depth, + string instance, + SelectListGroup group) + { + if (!tree.ContainsKey(parentId)) + return; + + foreach (var category in tree[parentId]) + { + string indent = new string('\u00A0', depth * 2); + + selectList.Add(new SelectListItem + { + Value = $"{instance}:{category.Id}", + Text = $"{indent}{category.Name}", + Group = group + }); + + BuildTree(selectList, tree, category.Id, depth + 1, instance, group); + } + } } } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/TreeBuilderHelper.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/TreeBuilderHelper.cs new file mode 100644 index 000000000..dae4fc31f --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/TreeBuilderHelper.cs @@ -0,0 +1,44 @@ +namespace LearningHub.Nhs.AdminUI.Helpers +{ + using LearningHub.Nhs.Models.Moodle; + using Microsoft.AspNetCore.Mvc.Rendering; + using System.Collections.Generic; + + /// + /// Defines the . + /// + public static class TreeBuilderHelper + { + /// + /// BuildSelectListFromCategories. + /// + /// The selectList. + /// The tree. + /// The parentId. + /// The depth. + /// The instance. + public static void BuildTree( + List selectList, + Dictionary> tree, + int parentId, + int depth, + string instance) + { + if (!tree.ContainsKey(parentId)) + return; + + foreach (var category in tree[parentId]) + { + string indent = new string('\u00A0', depth * 2); + + selectList.Add(new SelectListItem + { + Value = $"{instance}:{category.Id}", + Text = $"{indent}{category.Name}" + }); + + BuildTree(selectList, tree, category.Id, depth + 1, instance); + } + } + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleBridgeApiService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleBridgeApiService.cs new file mode 100644 index 000000000..f5e25b1cf --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Interfaces/IMoodleBridgeApiService.cs @@ -0,0 +1,19 @@ +namespace LearningHub.Nhs.AdminUI.Interfaces +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + + /// + /// IMoodleBridgeApiService. + /// + public interface IMoodleBridgeApiService + { + /// + /// GetAllMoodleCategoriesAsync. + /// + /// List of MoodleCategory. + Task> GetAllMoodleCategoriesAsync(); + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index a42f95f98..a8f070053 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user index 75a932fca..a753af4d7 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user @@ -4,7 +4,7 @@ ProjectDebugger - Local IIS + IIS Local MvcControllerEmptyScaffolder root/Common/MVC/Controller RazorViewEmptyScaffolder diff --git a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs index 9137a55e7..8db722e30 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs @@ -107,6 +107,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddTransient(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Configure Azure Search services.Configure(configuration.GetSection("AzureSearch")); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleBridgeApiService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleBridgeApiService.cs new file mode 100644 index 000000000..92d0677cb --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/MoodleBridgeApiService.cs @@ -0,0 +1,62 @@ +namespace LearningHub.Nhs.AdminUI.Services +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.AdminUI.Configuration; + using LearningHub.Nhs.AdminUI.Interfaces; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + + /// + /// MoodleBridgeApiService. + /// + public class MoodleBridgeApiService : IMoodleBridgeApiService + { + private readonly IOpenApiHttpClient openApiHttpClient; + + /// + /// Initializes a new instance of the class. + /// + /// The Open Api Http Client. + public MoodleBridgeApiService(IOpenApiHttpClient openApiHttpClient) + { + this.openApiHttpClient = openApiHttpClient; + } + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + public async Task> GetAllMoodleCategoriesAsync() + { + List viewmodel = new List(); + + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var request = $"MoodleBridge/GetAllMoodleCategories"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + catch (Exception ex) + { + return viewmodel; + } + } + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml index 81ea1901d..a2116742b 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/MoodleCategory.cshtml @@ -6,7 +6,8 @@ var page = CatalogueNavPage.Category; ViewData["Title"] = "Manage Category"; var selectedCategoryName = Model.MoodleCategorySelectList - .FirstOrDefault(c => c.Value == Model.SelectedCategoryId.ToString())?.Text; + .FirstOrDefault(c => c.Value == Model.SelectedCategoryId?.ToString())?.Text + ?? string.Empty; var errorHasOccurred = !ViewData.ModelState.IsValid; } @section SideMenu { @@ -40,7 +41,7 @@
- @if (Model.SelectedCategoryId > 0) + @if (Model.SelectedCategoryId != null) {
@@ -53,7 +54,7 @@
} -
+
@if (errorHasOccurred) { @@ -68,8 +69,18 @@
- + *@ + +
diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index 4c85e0949..23b92859c 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs b/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs new file mode 100644 index 000000000..2e7e4b566 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.WebUI.Configuration +{ + /// + /// The Moodle Bridge Settings. + /// + public class MoodleBridgeApiConfig + { + /////// + /////// Gets or sets the base url for the Moodle service. + /////// + ////public string BaseUrl { get; set; } = null!; + + /////// + /////// Gets or sets the Web service Rest Format. + /////// + ////public string MoodleWSRestFormat { get; set; } = null!; + + /////// + /////// Gets or sets the token. + /////// + ////public string WSToken { get; set; } = null!; + + /////// + /////// Gets or sets the token. + /////// + ////public string ApiPath { get; set; } = "webservice/rest/server.php"; + + /////// + /////// Gets or sets the token. + /////// + ////public string CoursePath { get; set; } = "course/view.php"; + } +} diff --git a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs index 1309ff694..2d7c2901b 100644 --- a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs @@ -67,6 +67,7 @@ public class AccountController : BaseController /// The specialtyService. /// The locationService. /// The gradeService. + /// The moodleBridgeApiService. public AccountController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -82,8 +83,9 @@ public AccountController( IMultiPageFormService multiPageFormService, ISpecialtyService specialtyService, ILocationService locationService, - IGradeService gradeService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IGradeService gradeService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.authConfig = authConfig; this.userService = userService; diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs index 64b6de28d..3d92a22ca 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs @@ -1,6 +1,7 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; @@ -44,34 +45,6 @@ public async Task GetActivityDetailed([FromBody] MyLearningRequest return this.Ok(activity); } - /// - /// Gets the detailed activity data. - /// - /// The request model - filter settings. - /// The . - [HttpPost] - [Route("GetUserRecentMyLearningActivities")] - public async Task GetUserRecentMyLearningActivities([FromBody] MyLearningRequestModel requestModel) - { - var activity = await this.myLearningService.GetUserRecentMyLearningActivities(requestModel); - - return this.Ok(activity); - } - - /// - /// Gets the detailed activity data. - /// - /// The request model - filter settings. - /// The . - [HttpPost] - [Route("GetUserLearningHistory")] - public async Task GetUserLearningHistory([FromBody] MyLearningRequestModel requestModel) - { - var activity = await this.myLearningService.GetUserLearningHistory(requestModel); - - return this.Ok(activity); - } - /// /// Gets the played segment data for the progress modal in My Learning screen. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/BaseController.cs b/LearningHub.Nhs.WebUI/Controllers/BaseController.cs index 6b3ad9a20..dd72350ec 100644 --- a/LearningHub.Nhs.WebUI/Controllers/BaseController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/BaseController.cs @@ -1,11 +1,16 @@ namespace LearningHub.Nhs.WebUI.Controllers { + using System.Collections.Generic; using System.Net.Http; + using System.Threading.Tasks; using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Extensions; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; + using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; @@ -20,25 +25,34 @@ public abstract class BaseController : Controller private readonly IWebHostEnvironment hostingEnvironment; private readonly IHttpClientFactory httpClientFactory; private readonly ILogger logger; + private readonly IMoodleBridgeApiService moodleBridgeApiService; private readonly Settings settings; private TenantViewModel currentTenant; + /// + /// The list of Moodle UserIds. + /// + private MoodleInstanceUserIdsViewModel moodleUserIds; + /// /// Initializes a new instance of the class. /// /// Hosting env. /// Http client factory. /// Logger. + /// moodleBridgeApiService. /// Settings. protected BaseController( IWebHostEnvironment hostingEnv, IHttpClientFactory httpClientFactory, ILogger logger, + IMoodleBridgeApiService moodleBridgeApiService, Settings settings) { this.hostingEnvironment = hostingEnv; this.httpClientFactory = httpClientFactory; this.logger = logger; + this.moodleBridgeApiService = moodleBridgeApiService; this.settings = settings; } @@ -82,6 +96,16 @@ protected BaseController( /// protected int CurrentMoodleUserId => this.User.Identity.GetMoodleUserId(); + /// + /// Gets the CurrentUserEmail. + /// + protected string CurrentUserEmail => this.User.Identity.GetCurrentEmail(); + + /// + /// Gets the MoodleInstanceUserIds. + /// + protected MoodleInstanceUserIdsViewModel MoodleInstanceUserIds => this.GetUserIdsPerInstances().Result; + /// /// The OnActionExecuting. /// @@ -92,6 +116,24 @@ public override void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionEx base.OnActionExecuting(context); } + /// + /// The get user ids and instances async. + /// + /// + /// The . + /// + public async Task GetUserIdsPerInstances() + { + if (this.moodleUserIds == null) + { + ////var moodleUserInstances = await this.moodleBridgeApiService.GetUserInstancesByEmail(this.CurrentUserEmail); + var moodleUserInstances = await this.moodleBridgeApiService.GetUserInstancesByEmail("binon.yesudhas@nhs.net"); + this.moodleUserIds = moodleUserInstances; + } + + return this.moodleUserIds; + } + /// /// The Initialise. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/BookmarkController.cs b/LearningHub.Nhs.WebUI/Controllers/BookmarkController.cs index 0d699fe4d..f41f2cb9b 100644 --- a/LearningHub.Nhs.WebUI/Controllers/BookmarkController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/BookmarkController.cs @@ -33,13 +33,15 @@ public partial class BookmarkController : BaseController /// The settings. /// The httpClientFactory. /// bookmarkService. + /// moodleBridgeApiService. public BookmarkController( IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, - IBookmarkService bookmarkService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IBookmarkService bookmarkService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.bookmarkService = bookmarkService; } diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index b6c3e82f6..c8156e23e 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -9,6 +9,8 @@ using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Databricks; + using LearningHub.Nhs.Models.Entities.Hierarchy; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Hierarchy; using LearningHub.Nhs.Models.Moodle; @@ -19,6 +21,7 @@ using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models.Catalogue; using LearningHub.Nhs.WebUI.Models.Search; + using LinqToTwitter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; @@ -42,7 +45,7 @@ public class CatalogueController : BaseController private IUserService userService; private IUserGroupService userGroupService; private IHierarchyService hierarchyService; - private Settings settings; + private Configuration.Settings settings; /// /// Initializes a new instance of the class. @@ -60,21 +63,23 @@ public class CatalogueController : BaseController /// HierarchyService. /// userGroupService. /// categoryService. + /// moodleBridgeApiService. public CatalogueController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, ILogger logger, ICatalogueService catalogueService, IUserService userService, - IOptions settings, + IOptions settings, LearningHubAuthServiceConfig authConfig, ISearchService searchService, ICacheService cacheService, IDashboardService dashboardService, IHierarchyService hierarchyService, IUserGroupService userGroupService, - ICategoryService categoryService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + ICategoryService categoryService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.authConfig = authConfig; this.catalogueService = catalogueService; @@ -192,13 +197,13 @@ public IActionResult CatalogueWithAuthentication(string reference) /// The tab name to display. /// The nodeId of the current folder. If not supplied, catalogue root contents are displayed. /// The SearchRequestViewModel. - /// The moodleCategoryId. + /// The moodleCategoryId. /// IActionResult. [AllowAnonymous] [ServiceFilter(typeof(SsoLoginFilterAttribute))] [HttpGet] [Route("catalogue/{reference}/{tab?}")] - public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search, int? moodleCategoryId) + public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search, string? categoryId) { if (tab == null || (tab == "search" && !this.User.Identity.IsAuthenticated)) { @@ -212,8 +217,9 @@ public async Task IndexAsync(string reference, string tab, int? n this.ViewBag.ActiveTab = tab; var catalogue = await this.catalogueService.GetCatalogueAsync(reference); - var catalogueCategoryId = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.Id); - catalogue.SelectedCategoryId = catalogueCategoryId; + var catalogueCategory = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.Id); + catalogue.SelectedCategoryId = catalogueCategory != null ? $"{catalogueCategory.InstanceName}:{catalogueCategory.CategoryId}" : null; + if (catalogue == null) { return this.RedirectToAction("Error", "Home"); @@ -270,22 +276,40 @@ public async Task IndexAsync(string reference, string tab, int? n bool includeEmptyFolder = viewModel.UserGroups.Any(x => x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.Previewer) || this.User.IsInRole("Administrator"); var nodeContents = await this.hierarchyService.GetNodeContentsForCatalogueBrowse(nodeId.Value, includeEmptyFolder); viewModel.NodeContents = nodeContents; - - int categoryId = moodleCategoryId ?? await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.Id); - if (categoryId > 0) + var catalogueNodeVersionCategory = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogue.Id); + string moodleInstanceCategoryId = null; + if (categoryId != null) { - var response = await this.categoryService.GetCoursesByCategoryIdAsync(categoryId); - viewModel.Courses = response.Courses; - - var subCategories = await this.categoryService.GetSubCategoryByCategoryIdAsync(categoryId); - viewModel.SubCategories = subCategories; + moodleInstanceCategoryId = $"{catalogueNodeVersionCategory.InstanceName}:{categoryId}"; + } + else + { + moodleInstanceCategoryId = catalogueNodeVersionCategory != null ? $"{catalogueNodeVersionCategory.InstanceName}:{catalogueNodeVersionCategory.CategoryId}" : null; + } - if (moodleCategoryId.HasValue) + if (!string.IsNullOrEmpty(moodleInstanceCategoryId)) + { + var response = await this.categoryService.GetCoursesByCategoryIdAsync(moodleInstanceCategoryId); + viewModel.Courses = response?.Results? + .Where(r => r.Instance == catalogueNodeVersionCategory.InstanceName) + .SelectMany(r => r.Data?.Courses.Courses ?? new List()) + .ToList(); + + var subCategories = await this.categoryService.GetSubCategoryByCategoryIdAsync(moodleInstanceCategoryId); + viewModel.SubCategories = subCategories + .Where(x => x.Instance == catalogueNodeVersionCategory.InstanceName && + x.Data?.Categories != null && + x.Data.Categories.Count > 0) + .SelectMany(x => x.Data.Categories) + .ToList(); + if (moodleInstanceCategoryId != null) { var moodleCategories = await this.categoryService.GetAllMoodleCategoriesAsync(); + var (moodleInstanceName, moodleCategoryId) = moodleInstanceCategoryId.Split(':') is var p && p.Length == 2 ? (p[0], p[1]) : (null, null); + // Start with the selected category - var breadcrumbCategory = moodleCategories.FirstOrDefault(x => x.Id == moodleCategoryId); + var breadcrumbCategory = moodleCategories.FirstOrDefault(x => x.Id == Convert.ToInt32(moodleCategoryId)); List categories = new List(); categories.Insert(0, new MoodleCategory @@ -299,12 +323,6 @@ public async Task IndexAsync(string reference, string tab, int? n // Add the current category to the breadcrumb list categories.Insert(1, breadcrumbCategory); - // If there's no parent, stop - if (breadcrumbCategory.Parent == 0 || breadcrumbCategory.Parent == catalogueCategoryId) - { - break; - } - // Move up one level breadcrumbCategory = moodleCategories.FirstOrDefault(x => x.Id == breadcrumbCategory.Parent); } @@ -324,7 +342,7 @@ public async Task IndexAsync(string reference, string tab, int? n } else { - viewModel.Catalogue.SelectedCategoryId = 0; + viewModel.Catalogue.SelectedCategoryId = null; } if (tab == "search") @@ -373,9 +391,14 @@ public async Task IndexAsync(string reference, string tab, int? n [Route("GetCourses/{catalogueNodeVerstionId}/{reference}/{tab}")] public async Task GetCourses(int catalogueNodeVerstionId, string reference, string tab) { - var categoryId = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogueNodeVerstionId); + var catalogueCategory = await this.categoryService.GetCatalogueVersionCategoryAsync(catalogueNodeVerstionId); + string categoryId = catalogueCategory.InstanceName + ":" + catalogueCategory.CategoryId; var response = await this.categoryService.GetCoursesByCategoryIdAsync(categoryId); - return this.PartialView("Courses", response.Courses); + var courses = response?.Results? + .Where(r => r.Instance == catalogueCategory.InstanceName) + .SelectMany(r => r.Data?.Courses.Courses ?? new List()) + .ToList(); + return this.PartialView("Courses", courses); } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs index 27ae615ce..e78a2c6b5 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs @@ -44,6 +44,7 @@ public class ContributeController : BaseController /// Azure media service. /// Auth config. /// Contribure service. + /// moodleBridgeApiService. public ContributeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -55,8 +56,9 @@ public ContributeController( IResourceService resourceService, IAzureMediaService azureMediaService, LearningHubAuthServiceConfig authConfig, - IContributeService contributeService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IContributeService contributeService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.authConfig = authConfig; diff --git a/LearningHub.Nhs.WebUI/Controllers/ContributeResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/ContributeResourceController.cs index cd9ec928c..ea546cc5e 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ContributeResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ContributeResourceController.cs @@ -39,14 +39,16 @@ public class ContributeResourceController : BaseController /// Http client factory. /// Logger. /// Settings. + /// moodleBridgeApiService. public ContributeResourceController( IResourceService resourceService, ICatalogueService catalogueService, IWebHostEnvironment hostingEnv, + IMoodleBridgeApiService moodleBridgeApiService, IHttpClientFactory httpClientFactory, ILogger logger, IOptions settings) - : base(hostingEnv, httpClientFactory, logger, settings.Value) + : base(hostingEnv, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.resourceService = resourceService; this.catalogueService = catalogueService; diff --git a/LearningHub.Nhs.WebUI/Controllers/FileController.cs b/LearningHub.Nhs.WebUI/Controllers/FileController.cs index e314d1525..5106aa645 100644 --- a/LearningHub.Nhs.WebUI/Controllers/FileController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/FileController.cs @@ -25,13 +25,15 @@ public class FileController : BaseController /// The settings. /// The httpClientFactory. /// The fileService. + /// moodleBridgeApiService. public FileController( IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, - IFileService fileService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IFileService fileService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.fileService = fileService; } diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 5f4540b57..b9f2967c1 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -50,6 +50,7 @@ public class HomeController : BaseController /// Http client factory. /// Hosting environment. /// Logger. + /// moodleBridgeApiService. /// Settings. /// User service. /// Resource service. @@ -62,6 +63,7 @@ public class HomeController : BaseController public HomeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, + IMoodleBridgeApiService moodleBridgeApiService, ILogger logger, IOptions settings, IUserService userService, @@ -72,7 +74,7 @@ public HomeController( IFeatureManager featureManager, IUserGroupService userGroupService, Microsoft.Extensions.Configuration.IConfiguration configuration) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.authConfig = authConfig; this.userService = userService; @@ -215,7 +217,7 @@ public async Task Index(string dashboardTrayLearningResourceType this.Logger.LogInformation("User is authenticated: User is {fullname} and userId is: {lhuserid}", this.User.Identity.GetCurrentName(), this.User.Identity.GetCurrentUserId()); if (this.User.IsInRole("Administrator") || this.User.IsInRole("BlueUser") || this.User.IsInRole("ReadOnly") || this.User.IsInRole("BasicUser")) { - var learningTask = this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashboard, 1); + var learningTask = this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashboard, this.CurrentUserEmail, 1); var resourcesTask = this.dashboardService.GetResourcesAsync(resourceDashboard, 1); var cataloguesTask = this.dashboardService.GetCataloguesAsync(catalogueDashboard, 1); var userGroupsTask = this.userGroupService.UserHasCatalogueContributionPermission(); @@ -282,7 +284,7 @@ public async Task LoadPage(string dashboardTrayLearningResourceTy switch (dashBoardTray) { case "my-learning": - model.MyLearnings = await this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashBoard, pageNumber); + model.MyLearnings = await this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashBoard, this.CurrentUserEmail, pageNumber); return this.PartialView("_MyCoursesAndElearning", model); case "resources": model.Resources = await this.dashboardService.GetResourcesAsync(resourceDashBoard, pageNumber); @@ -294,7 +296,7 @@ public async Task LoadPage(string dashboardTrayLearningResourceTy } else { - var learningTask = this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashBoard, dashBoardTray == "my-learning" ? pageNumber : 1); + var learningTask = this.dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, myLearningDashBoard, this.CurrentUserEmail, dashBoardTray == "my-learning" ? pageNumber : 1); var resourcesTask = this.dashboardService.GetResourcesAsync(resourceDashBoard, dashBoardTray == "resources" ? pageNumber : 1); var cataloguesTask = this.dashboardService.GetCataloguesAsync(catalogueDashBoard, dashBoardTray == "catalogues" ? pageNumber : 1); await Task.WhenAll(learningTask, resourcesTask, cataloguesTask); diff --git a/LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs b/LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs index 2ffc17e13..f1bc5c20d 100644 --- a/LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs @@ -60,6 +60,7 @@ public class LoginWizardController : BaseController /// The specialtyService. /// The locationService. /// The gradeService. + /// moodleBridgeApiService. public LoginWizardController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -75,8 +76,9 @@ public LoginWizardController( IMultiPageFormService multiPageFormService, ISpecialtyService specialtyService, ILocationService locationService, - IGradeService gradeService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IGradeService gradeService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.userService = userService; this.termsAndConditionService = termsAndConditionService; diff --git a/LearningHub.Nhs.WebUI/Controllers/MediaController.cs b/LearningHub.Nhs.WebUI/Controllers/MediaController.cs index 4acd69e07..537fb5844 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MediaController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MediaController.cs @@ -26,8 +26,9 @@ public class MediaController : BaseController /// Settings. /// Http client factory. /// Azure media services. - public MediaController(IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IAzureMediaService azureMediaService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + /// moodleBridgeApiService. + public MediaController(IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IAzureMediaService azureMediaService, IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.azureMediaService = azureMediaService; this.logger = logger; diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index a91718384..bb24c03f8 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -46,12 +46,14 @@ public partial class MyAccountController : BaseController private readonly ILocationService locationService; private readonly ICacheService cacheService; private readonly IConfiguration configuration; + private readonly IMoodleBridgeApiService moodleBridgeApiService; /// /// Initializes a new instance of the class. /// /// The hostingEnvironment. /// The logger. + /// moodleBridgeApiService. /// The settings. /// The httpClientFactory. /// userService. @@ -80,8 +82,9 @@ public MyAccountController( ILocationService locationService, IMultiPageFormService multiPageFormService, ICacheService cacheService, - IConfiguration configuration) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IConfiguration configuration, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.userService = userService; this.loginWizardService = loginWizardService; diff --git a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs index 3dc2fe732..9e8dc29c8 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs @@ -53,8 +53,9 @@ public class MyLearningController : BaseController /// user Service. /// PDF Report Service. /// fileService. - public MyLearningController(IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IMyLearningService myLearningService, IResourceService resourceService, IHierarchyService hierarchyService, IUserService userService, IPDFReportService pdfReportService, IFileService fileService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + /// moodleBridgeApiService. + public MyLearningController(IWebHostEnvironment hostingEnvironment, IMoodleBridgeApiService moodleBridgeApiService, ILogger logger, IOptions settings, IHttpClientFactory httpClientFactory, IMyLearningService myLearningService, IResourceService resourceService, IHierarchyService hierarchyService, IUserService userService, IPDFReportService pdfReportService, IFileService fileService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.myLearningService = myLearningService; this.resourceService = resourceService; @@ -227,7 +228,7 @@ public async Task Index(MyLearningUserActivitiesViewModel learnin break; } - var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel); + var result = await this.myLearningService.GetUserRecentMyLearningActivities(myLearningRequestModel, this.MoodleInstanceUserIds); var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); if (result != null) @@ -374,7 +375,7 @@ public async Task LearningHistory(MyLearningUserActivitiesViewMod break; } - var result = await this.myLearningService.GetUserLearningHistory(myLearningRequestModel); + var result = await this.myLearningService.GetUserLearningHistory(myLearningRequestModel, this.MoodleInstanceUserIds); var response = new MyLearningUserActivitiesViewModel(myLearningRequestModel); if (result != null) @@ -457,7 +458,7 @@ public async Task DownloadActivities(MyLearningRequestModel myLea filter.Take = 999; var userDetails = await this.userService.GetCurrentUserBasicDetailsAsync(); var response = new MyLearningUserActivitiesViewModel(); - var result = await this.myLearningService.GetUserLearningHistory(filter); + var result = await this.myLearningService.GetUserLearningHistory(filter, this.MoodleInstanceUserIds); if (result != null) { response.TotalCount = result.TotalCount; @@ -611,7 +612,7 @@ public async Task Certificates(MyLearningUserCertificatesViewMode break; } - var result = await this.myLearningService.GetUserCertificateDetails(myLearningRequestModel); + var result = await this.myLearningService.GetUserCertificateDetails(myLearningRequestModel, this.MoodleInstanceUserIds); var response = new MyLearningUserCertificatesViewModel(myLearningRequestModel); if (result != null) diff --git a/LearningHub.Nhs.WebUI/Controllers/NotificationController.cs b/LearningHub.Nhs.WebUI/Controllers/NotificationController.cs index c5013e07f..627154c03 100644 --- a/LearningHub.Nhs.WebUI/Controllers/NotificationController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/NotificationController.cs @@ -32,13 +32,15 @@ public class NotificationController : BaseController /// Settings. /// Notification service. /// Logger. + /// moodleBridgeApiService. public NotificationController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, IOptions settings, INotificationService notificationService, + IMoodleBridgeApiService moodleBridgeApiService, ILogger logger) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.notificationService = notificationService; } diff --git a/LearningHub.Nhs.WebUI/Controllers/OfflineController.cs b/LearningHub.Nhs.WebUI/Controllers/OfflineController.cs index 178fa5c0b..4283d3279 100644 --- a/LearningHub.Nhs.WebUI/Controllers/OfflineController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/OfflineController.cs @@ -26,13 +26,15 @@ public class OfflineController : BaseController /// The logger. /// The settings. /// The internalSystemService. + /// moodleBridgeApiService. public OfflineController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, - IInternalSystemService internalSystemService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IInternalSystemService internalSystemService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.internalSystemService = internalSystemService; } diff --git a/LearningHub.Nhs.WebUI/Controllers/OpenAthensController.cs b/LearningHub.Nhs.WebUI/Controllers/OpenAthensController.cs index f046d8680..2ec1a4c4f 100644 --- a/LearningHub.Nhs.WebUI/Controllers/OpenAthensController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/OpenAthensController.cs @@ -23,6 +23,7 @@ public class OpenAthensController : BaseController { private readonly IUserService userService; private readonly LearningHubAuthServiceConfig authConfig; + private readonly IMoodleBridgeApiService moodleBridgeApiService; /// /// Initializes a new instance of the class. @@ -33,14 +34,16 @@ public class OpenAthensController : BaseController /// The env. /// The http client factory. /// The settings. + /// moodleBridgeApiService. public OpenAthensController( ILogger logger, IUserService userService, + IMoodleBridgeApiService moodleBridgeApiService, LearningHubAuthServiceConfig authConfig, IWebHostEnvironment env, IHttpClientFactory httpClientFactory, IOptions settings) - : base(env, httpClientFactory, logger, settings.Value) + : base(env, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.userService = userService; this.authConfig = authConfig; diff --git a/LearningHub.Nhs.WebUI/Controllers/PoliciesController.cs b/LearningHub.Nhs.WebUI/Controllers/PoliciesController.cs index 9f619e572..ef5c6e300 100644 --- a/LearningHub.Nhs.WebUI/Controllers/PoliciesController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/PoliciesController.cs @@ -25,13 +25,15 @@ public class PoliciesController : BaseController /// Logger. /// Settings. /// Terms and conditions service. + /// moodleBridgeApiService. public PoliciesController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, - ITermsAndConditionsService termsAndConditionsService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + ITermsAndConditionsService termsAndConditionsService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.termsAndConditionsService = termsAndConditionsService; } diff --git a/LearningHub.Nhs.WebUI/Controllers/ReportsController.cs b/LearningHub.Nhs.WebUI/Controllers/ReportsController.cs index 4cda20cb7..c67c7a6b8 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ReportsController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ReportsController.cs @@ -37,6 +37,7 @@ public class ReportsController : BaseController private readonly IMultiPageFormService multiPageFormService; private readonly IReportService reportService; private readonly IFileService fileService; + private readonly IMoodleBridgeApiService moodleBridgeApiService; /// /// Initializes a new instance of the class. @@ -50,8 +51,9 @@ public class ReportsController : BaseController /// The hostingEnvironment. /// The logger. /// settings. - public ReportsController(IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, ILogger logger, IOptions settings, ICacheService cacheService, IMultiPageFormService multiPageFormService, IReportService reportService, ICategoryService categoryService, IFileService fileService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + /// moodleBridgeApiService. + public ReportsController(IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, IMoodleBridgeApiService moodleBridgeApiService, ILogger logger, IOptions settings, ICacheService cacheService, IMultiPageFormService multiPageFormService, IReportService reportService, ICategoryService categoryService, IFileService fileService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.cacheService = cacheService; this.multiPageFormService = multiPageFormService; @@ -434,14 +436,15 @@ public async Task CourseProgressReport(CourseCompletionViewModel private async Task>> GetCoursesAsync() { - int categoryId = this.Settings.StatMandId; + ////int categoryId = this.Settings.StatMandId; + string categoryId = "test"; var courses = new List>(); var subCategories = await this.categoryService.GetCoursesByCategoryIdAsync(categoryId); - foreach (var subCategory in subCategories.Courses) - { - courses.Add(new KeyValuePair(subCategory.Id.ToString(), UtilityHelper.ConvertToSentenceCase(subCategory.Displayname))); - } + ////foreach (var subCategory in subCategories.Courses) + ////{ + //// courses.Add(new KeyValuePair(subCategory.Id.ToString(), UtilityHelper.ConvertToSentenceCase(subCategory.Displayname))); + ////} return courses; } diff --git a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs index 5df61185e..00d1f0f74 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs @@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; + using Microsoft.Azure.Management.Media.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.FeatureManagement; @@ -63,6 +64,7 @@ public class ResourceController : BaseController /// The fileService. /// The cacheService. /// The Feature flag manager. + /// moodleBridgeApiService. public ResourceController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -79,8 +81,9 @@ public ResourceController( IHierarchyService hierarchyService, IFileService fileService, ICacheService cacheService, + IMoodleBridgeApiService moodleBridgeApiService, IFeatureManager featureManager) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.azureMediaService = azureMediaService; this.resourceService = resourceService; diff --git a/LearningHub.Nhs.WebUI/Controllers/RoadMapController.cs b/LearningHub.Nhs.WebUI/Controllers/RoadMapController.cs index b9bcf8a22..d34e1f280 100644 --- a/LearningHub.Nhs.WebUI/Controllers/RoadMapController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/RoadMapController.cs @@ -1,6 +1,7 @@ namespace LearningHub.Nhs.WebUI.Controllers { using System.Net.Http; + using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -11,6 +12,8 @@ /// public class RoadMapController : BaseController { + private readonly IMoodleBridgeApiService moodleBridgeApiService; + /// /// Initializes a new instance of the class. /// @@ -18,12 +21,14 @@ public class RoadMapController : BaseController /// Http client factory. /// Logger. /// Settings. + /// moodleBridgeApiService. public RoadMapController( IWebHostEnvironment hostingEnvironment, + IMoodleBridgeApiService moodleBridgeApiService, IHttpClientFactory httpClientFactory, ILogger logger, IOptions settings) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { } diff --git a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs index 074bc39ab..3f4cf8766 100644 --- a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs @@ -42,6 +42,7 @@ public class SearchController : BaseController /// The logger. /// The fileService. /// The Feature flag manager. + /// moodleBridgeApiService. public SearchController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -49,8 +50,9 @@ public SearchController( ISearchService searchService, ILogger logger, IFileService fileService, + IMoodleBridgeApiService moodleBridgeApiService, IFeatureManager featureManager) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.searchService = searchService; this.fileService = fileService; diff --git a/LearningHub.Nhs.WebUI/Controllers/TrackingController.cs b/LearningHub.Nhs.WebUI/Controllers/TrackingController.cs index 4463faf73..a65d56864 100644 --- a/LearningHub.Nhs.WebUI/Controllers/TrackingController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/TrackingController.cs @@ -14,6 +14,7 @@ namespace LearningHub.Nhs.WebUI.Controllers public class TrackingController : BaseController { private readonly IJsDetectionLogger jsDetectionLogger; + private readonly IMoodleBridgeApiService moodleBridgeApiService; /// /// Initializes a new instance of the class. @@ -23,13 +24,15 @@ public class TrackingController : BaseController /// The settings. /// The logger. /// The JsDetectionLogger. + /// moodleBridgeApiService. public TrackingController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, + IMoodleBridgeApiService moodleBridgeApiService, IOptions settings, ILogger logger, IJsDetectionLogger jsDetectionLogger) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.jsDetectionLogger = jsDetectionLogger; } diff --git a/LearningHub.Nhs.WebUI/Controllers/UserController.cs b/LearningHub.Nhs.WebUI/Controllers/UserController.cs index 284c2a0b9..75307e7fe 100644 --- a/LearningHub.Nhs.WebUI/Controllers/UserController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/UserController.cs @@ -64,6 +64,7 @@ public partial class UserController : BaseController /// countryService. /// regionService. /// The multiPageFormService. + /// moodleBridgeApiService. public UserController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -73,8 +74,9 @@ public UserController( ILoginWizardService loginWizardService, ICountryService countryService, IRegionService regionService, - IMultiPageFormService multiPageFormService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + IMultiPageFormService multiPageFormService, + IMoodleBridgeApiService moodleBridgeApiService) + : base(hostingEnvironment, httpClientFactory, logger, moodleBridgeApiService, settings.Value) { this.userService = userService; this.loginWizardService = loginWizardService; diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs index 37e87d072..d3e0c4c5a 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ICategoryService.cs @@ -18,21 +18,21 @@ public interface ICategoryService /// /// catalogueNodeVersionId. /// A representing the result of the asynchronous operation. - Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId); + Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId); /// /// GetCoursesByCategoryIdAsync. /// /// categoryId. /// A representing the result of the asynchronous operation. - Task GetCoursesByCategoryIdAsync(int categoryId); + Task GetCoursesByCategoryIdAsync(string categoryId); /// /// GetSubCategoryByCategoryIdAsync. /// /// categoryId. /// A representing the result of the asynchronous operation. - Task> GetSubCategoryByCategoryIdAsync(int categoryId); + Task> GetSubCategoryByCategoryIdAsync(string categoryId); /// /// GetAllMoodleCategoriesAsync. diff --git a/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs b/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs index 811dd1133..d4ce69b83 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.WebUI.Models; @@ -24,9 +25,10 @@ public interface IDashboardService /// /// The dashboardTrayLearningResource type. /// The dashboard type. + /// The email. /// The page Number. /// A representing the result of the asynchronous operation. - Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, int pageNumber); + Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, string email, int pageNumber); /// /// GetCataloguesAsync. diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiHttpClient.cs new file mode 100644 index 000000000..bbf712b87 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiHttpClient.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.WebUI.Interfaces +{ + using System.Net.Http; + using System.Threading.Tasks; + + /// + /// The OpenApiHttpClient interface. + /// + public interface IMoodleBridgeApiHttpClient + { + /// + /// The get client. + /// + /// The . + Task GetClientAsync(); + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs new file mode 100644 index 000000000..e316bbf84 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.WebUI.Interfaces +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.WebUI.Models; + using MoodleCourseCompletionModel = LearningHub.Nhs.Models.Moodle.API.MoodleCourseCompletionModel; + + /// + /// IMoodleApiService. + /// + public interface IMoodleBridgeApiService + { + /// + /// GetUserInstancesByEmailAsync. + /// + /// The email. + /// A representing the result of the asynchronous operation. + Task GetUserInstancesByEmail(string email); + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs index e9b584cc4..3a415f9fb 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMyLearningService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.MyLearning; /// @@ -21,15 +22,17 @@ public interface IMyLearningService /// Gets the user recent my leraning activities.. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel); + Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds); /// /// Gets the user leraning history. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - Task GetUserLearningHistory(MyLearningRequestModel requestModel); + Task GetUserLearningHistory(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds); /// /// Gets the played segment data for the progress modal in My Learning screen. @@ -53,8 +56,9 @@ public interface IMyLearningService /// Gets the user certificates. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - Task GetUserCertificateDetails(MyLearningRequestModel requestModel); + Task GetUserCertificateDetails(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds); /// /// Gets the resource URL for a given resource reference ID. diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index c8016ba86..f0521282a 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -112,7 +112,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs index 51c66fde4..1d9617c5a 100644 --- a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs @@ -57,7 +57,7 @@ public class CatalogueIndexViewModel /// /// Gets or sets the courses in the catalogue. /// - public List SubCategories { get; set; } + public List SubCategories { get; set; } /// /// Gets or sets the moodle categories. diff --git a/LearningHub.Nhs.WebUI/Services/CategoryService.cs b/LearningHub.Nhs.WebUI/Services/CategoryService.cs index b68a794ca..7179f8c37 100644 --- a/LearningHub.Nhs.WebUI/Services/CategoryService.cs +++ b/LearningHub.Nhs.WebUI/Services/CategoryService.cs @@ -42,18 +42,18 @@ public CategoryService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHtt /// /// The catalogueNodeVersionId. /// A representing the result of the asynchronous operation. - public async Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId) + public async Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersionId) { + CatalogueNodeVersionCategory catalogueNodeVersionCategory = new CatalogueNodeVersionCategory { }; var client = await this.OpenApiHttpClient.GetClientAsync(); var request = $"category/GetCatalogueVersionCategory/{catalogueNodeVersionId}"; var response = await client.GetAsync(request).ConfigureAwait(false); - var categoryId = 0; if (response.IsSuccessStatusCode) { var result = response.Content.ReadAsStringAsync().Result; - categoryId = Convert.ToInt32(result); + catalogueNodeVersionCategory = JsonConvert.DeserializeObject(result); } else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) @@ -61,7 +61,7 @@ public async Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersion throw new Exception("AccessDenied"); } - return categoryId; + return catalogueNodeVersionCategory; } /// @@ -69,9 +69,9 @@ public async Task GetCatalogueVersionCategoryAsync(int catalogueNodeVersion /// /// The categoryId. /// A representing the result of the asynchronous operation. - public async Task> GetSubCategoryByCategoryIdAsync(int categoryId) + public async Task> GetSubCategoryByCategoryIdAsync(string categoryId) { - List viewmodel = new List { }; + List viewmodel = new List { }; var client = await this.OpenApiHttpClient.GetClientAsync(); @@ -81,7 +81,7 @@ public async Task> GetSubCategoryByCategory if (response.IsSuccessStatusCode) { var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject>(result); + viewmodel = JsonConvert.DeserializeObject>(result); } else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) @@ -97,9 +97,9 @@ public async Task> GetSubCategoryByCategory /// /// The categoryId. /// A representing the result of the asynchronous operation. - public async Task GetCoursesByCategoryIdAsync(int categoryId) + public async Task GetCoursesByCategoryIdAsync(string categoryId) { - MoodleCoursesResponseModel viewmodel = new MoodleCoursesResponseModel { }; + MoodleCourseResultsResponseModel viewmodel = new MoodleCourseResultsResponseModel { }; var client = await this.OpenApiHttpClient.GetClientAsync(); @@ -109,7 +109,7 @@ public async Task GetCoursesByCategoryIdAsync(int ca if (response.IsSuccessStatusCode) { var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); + viewmodel = JsonConvert.DeserializeObject(result); } else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index d201da1f2..1412dcc9b 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -2,11 +2,14 @@ { using System; using System.Collections.Generic; + using System.Net.Http.Json; using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Entities.Analytics; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; @@ -66,16 +69,25 @@ public async Task GetMyAccessLearningsAsyn /// /// The dashboard Tray Learning Resource type. /// The dashboard type. + /// The email. /// The page Number. /// A representing the result of the asynchronous operation. - public async Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, int pageNumber) + public async Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, string email, int pageNumber) { DashboardMyLearningResponseViewModel viewmodel = new DashboardMyLearningResponseViewModel { }; var client = await this.OpenApiHttpClient.GetClientAsync(); + var request = new GetMyCoursesAndElearningRequestModel + { + DashboardTrayLearningResourceType = dashboardTrayLearningResourceType, + DashboardType = dashboardType, + Email = email, + PageNumber = pageNumber, + }; - var request = $"dashboard/GetMyCoursesAndElearning/{dashboardTrayLearningResourceType}/{dashboardType}/{pageNumber}"; - var response = await client.GetAsync(request).ConfigureAwait(false); + //// var request = $"dashboard/GetMyCoursesAndElearning/{dashboardTrayLearningResourceType}/{dashboardType}/{email}/{pageNumber}"; + //// var response = await client.GetAsync(request).ConfigureAwait(false); + var response = await client.PostAsJsonAsync("dashboard/GetMyCoursesAndElearning", request); if (response.IsSuccessStatusCode) { diff --git a/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiHttpClient.cs b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiHttpClient.cs new file mode 100644 index 000000000..04f91cdd8 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiHttpClient.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 MoodleBridgeApiHttpClient : 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 MoodleBridgeApiHttpClient( + 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/MoodleBridgeApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs new file mode 100644 index 000000000..f00592e01 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs @@ -0,0 +1,67 @@ +namespace LearningHub.Nhs.WebUI.Services +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.WebUI.Configuration; + using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.WebUI.Models; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + + /// + /// MoodleApiService. + /// + public class MoodleBridgeApiService : IMoodleBridgeApiService + { + private readonly IOpenApiHttpClient openApiHttpClient; + //// private readonly MoodleBridgeApiConfig configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The open Api Http Client. + public MoodleBridgeApiService(IOpenApiHttpClient openApiHttpClient) + { + this.openApiHttpClient = openApiHttpClient; + ////this.configuration = configuration.Value; + } + + /// + /// GetUserInstancesByEmailAsync. + /// + /// The email. + /// UserId from Moodle. + public async Task GetUserInstancesByEmail(string email) + { + MoodleInstanceUserIdsViewModel viewmodel = null; + + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var request = $"MoodleBridge/GetUserInstancesByEmail/{email}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; + } + catch (Exception ex) + { + return viewmodel; + } + } + } +} diff --git a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs index cb75f1530..2014a2af0 100644 --- a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs @@ -5,9 +5,11 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -68,12 +70,19 @@ public async Task GetActivityDetailed(MyLearningReq /// Gets the user recent my leraning activities. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - public async Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel) + public async Task GetUserRecentMyLearningActivities(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds) { MyLearningActivitiesDetailedViewModel viewModel = null; - var json = JsonConvert.SerializeObject(requestModel); + var payload = new MyLearningApiRequestViewModel + { + Request = requestModel, + MoodleInstanceUserIds = moodleInstanceUserIds, + }; + + var json = JsonConvert.SerializeObject(payload); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); var client = await this.OpenApiHttpClient.GetClientAsync(); @@ -83,7 +92,7 @@ public async Task GetUserRecentMyLearning if (response.IsSuccessStatusCode) { - var result = response.Content.ReadAsStringAsync().Result; + var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); viewModel = JsonConvert.DeserializeObject(result); } else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized @@ -100,12 +109,18 @@ public async Task GetUserRecentMyLearning /// Gets the user recent my leraning activities. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - public async Task GetUserLearningHistory(MyLearningRequestModel requestModel) + public async Task GetUserLearningHistory(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds) { MyLearningActivitiesDetailedViewModel viewModel = null; + var payload = new MyLearningApiRequestViewModel + { + Request = requestModel, + MoodleInstanceUserIds = moodleInstanceUserIds, + }; - var json = JsonConvert.SerializeObject(requestModel); + var json = JsonConvert.SerializeObject(payload); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); var client = await this.OpenApiHttpClient.GetClientAsync(); @@ -192,12 +207,17 @@ public async Task> GetResourceCertif /// Gets the user certificates. /// /// The request model. + /// The moodleInstanceUserIds. /// The . - public async Task GetUserCertificateDetails(MyLearningRequestModel requestModel) + public async Task GetUserCertificateDetails(MyLearningRequestModel requestModel, MoodleInstanceUserIdsViewModel moodleInstanceUserIds) { MyLearningCertificatesDetailedViewModel viewModel = null; - - var json = JsonConvert.SerializeObject(requestModel); + var payload = new MyLearningApiRequestViewModel + { + Request = requestModel, + MoodleInstanceUserIds = moodleInstanceUserIds, + }; + var json = JsonConvert.SerializeObject(payload); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); var client = await this.OpenApiHttpClient.GetClientAsync(); diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index 60ff5f634..589eea428 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -101,6 +101,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml index f8c67dc63..255049950 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml @@ -15,12 +15,12 @@

Courses

- @foreach (MoodleSubCategoryResponseModel item in Model.SubCategories) + @foreach (MoodleCategory item in Model.SubCategories) {
diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml index 1384acfb9..d08f7769d 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml @@ -57,8 +57,8 @@ if (ViewBag.ActiveTab == "courses") { - if (Model.Catalogue.SelectedCategoryId == 0 || - (Model.Catalogue.SelectedCategoryId > 0 && + if (Model.Catalogue.SelectedCategoryId == null || + (Model.Catalogue.SelectedCategoryId != null && (Model.Courses == null || !Model.Courses.Any()) && (Model.SubCategories == null || !Model.SubCategories.Any()))) { @@ -71,7 +71,7 @@ { breadcrumbs = UtilityHelper.GetBreadcrumbsForFolderNodes(Model.NodePathNodes.SkipLast(1).ToList(), Model.Catalogue.Url); } - else if (ViewBag.ActiveTab == "courses" && Model.Catalogue.SelectedCategoryId > 0) + else if (ViewBag.ActiveTab == "courses" && Model.Catalogue.SelectedCategoryId !=null) { breadcrumbs = UtilityHelper.GetBreadcrumbsForCourses(Model.MoodleCategories, Model.Catalogue.Url); } @@ -205,7 +205,7 @@ Resources } - @if (Model.Catalogue.SelectedCategoryId > 0 && + @if (Model.Catalogue.SelectedCategoryId != null && ( (Model.Courses != null && Model.Courses.Any()) || (Model.SubCategories != null && Model.SubCategories.Any()) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleBridgeConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleBridgeConfig.cs new file mode 100644 index 000000000..4798fabe8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleBridgeConfig.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The Moodle Bridge Settings. + /// + public class MoodleBridgeAPIConfig + { + /// + /// Gets or sets the base url for the Moodle service. + /// + public string BaseUrl { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string Token { get; set; } = null!; + } +} 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 16df89013..b147cf34f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -17,8 +17,12 @@ - + + + + + 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 cbac27a64..acfceeb83 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 @@ -17,7 +17,7 @@ - + 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 6a727dc60..33302f623 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -24,7 +24,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs index 3367b371f..32932e648 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -226,12 +226,14 @@ public async Task AddCategoryToCatalogueAsync(int userId, CatalogueViewModel vm) { try { + var (instanceName, categoryId) = vm.SelectedCategoryId.Split(':') is var p && p.Length == 2 ? (p[0], p[1]) : (null, null); var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = vm.CatalogueNodeVersionId }; - var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = vm.SelectedCategoryId }; - var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = categoryId }; + var param3 = new SqlParameter("@p3", SqlDbType.VarChar) { Value = instanceName }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; - await DbContext.Database.ExecuteSqlRawAsync("hierarchy.CatalogueNodeVersionCategoryCreate @p0, @p1, @p2, @p3", param0, param1, param2, param3); + await DbContext.Database.ExecuteSqlRawAsync("hierarchy.CatalogueNodeVersionCategoryCreate @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4); } catch (Exception ex) { @@ -249,9 +251,10 @@ public async Task RemoveCategoryFromCatalogueAsync(int userId, CatalogueViewMode { try { + var (instanceName, categoryId) = vm.SelectedCategoryId.Split(':') is var p && p.Length == 2 ? (p[0], p[1]) : (null, null); var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = vm.CatalogueNodeVersionId }; - var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = vm.SelectedCategoryId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = categoryId }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; await DbContext.Database.ExecuteSqlRawAsync("hierarchy.RemoveCatalogueCategory @p0, @p1, @p2, @p3", param0, param1, param2, param3); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleBridgeHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleBridgeHttpClient.cs new file mode 100644 index 000000000..129d3eec3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleBridgeHttpClient.cs @@ -0,0 +1,25 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Services.Interface.HttpClients +{ + /// + /// The Moodle Http Client interface. + /// + public interface IMoodleBridgeHttpClient + { + /// + /// The get cient async. + /// + /// The . + Task GetClient(); + + /// + /// GETs data from Databricks API. + /// + /// The URL to make a get call to. + /// Optional authorization header. + /// A representing the result of the asynchronous operation. + Task GetData(string requestUrl, string? authHeader); + } +} 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 175ac7fb3..802661079 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 @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICategoryService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICategoryService.cs index 5c4a994a4..2ac311640 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICategoryService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ICategoryService.cs @@ -14,6 +14,6 @@ public interface ICategoryService /// /// node version id. /// A representing the result of the asynchronous operation. - Task GetByCatalogueVersionIdAsync(int nodeVersionId); + Task GetByCatalogueVersionIdAsync(int nodeVersionId); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs index 7897acf90..ff04ae056 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IDashboardService.cs @@ -21,13 +21,11 @@ public interface IDashboardService /// /// Get My in progress courses and Elearning. /// - /// The dashboardTrayLearningResource type. - /// The dashboard type. - /// The page Number. + /// The requestModel. /// The userId. /// The resourceType. /// A representing the result of the asynchronous operation. - Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, int pageNumber, int userId, string resourceType); + Task GetMyCoursesAndElearning(GetMyCoursesAndElearningRequestModel requestModel, int userId, string resourceType); /// /// Gets the user in progrss my leraning activities.. @@ -35,8 +33,9 @@ public interface IDashboardService /// The dashboardTrayLearningResourceType. /// The pageNumber. /// The user id. + /// The email. /// The . - Task GetMyInprogressLearningAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId); + Task GetMyInprogressLearningAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId, string email); /// /// Gets the resource certificate details. @@ -45,7 +44,7 @@ public interface IDashboardService /// The user id. /// The resourceType. /// The . - Task GetUserCertificateDetailsAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId); + Task GetUserCertificateDetailsAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId, string email); /// /// GetResources. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs new file mode 100644 index 000000000..e099e8bb9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs @@ -0,0 +1,82 @@ + +using System.Collections.Generic; +using System.Threading.Tasks; +using LearningHub.Nhs.Models.Moodle; +using LearningHub.Nhs.Models.Moodle.API; +using LearningHub.Nhs.Models.MyLearning; + +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + /// + /// IMoodleBridgeApiService. + /// + public interface IMoodleBridgeApiService + { + /// + /// GetUserInstancesByEmailAsync. + /// + /// The current LH User email. + /// A representing the result of the asynchronous operation. + Task GetUserInstancesByEmail(string email); + + /// + /// GetRecentEnrolledCoursesAsync. + /// + /// The moodleUserInstanceUserIds. + /// The requestModel. + /// The month. + /// + Task GetRecentEnrolledCoursesAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, MyLearningRequestModel requestModel, int? month = null); + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + Task> GetAllMoodleCategoriesAsync(); + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle Instances user id. + /// MyLearningRequestModel requestModel. + /// A representing the result of the asynchronous operation. + Task GetEnrolledCoursesHistoryAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, MyLearningRequestModel requestModel); + + /// + /// GetInProgressEnrolledCoursesAsync. + /// + /// The email. + /// A representing the result of the asynchronous operation. + Task GetInProgressEnrolledCoursesAsync(string email); + + /// + /// GetUserLearningHistory. + /// + /// user email. + /// The filterText. + /// A representing the result of the asynchronous operation. + Task GetUserCertificateAsync(string email, string filterText = ""); + + /// + /// GetUserCertificateFromMoodleInstancesAsync. + /// + /// Moodle Instances user id. + /// MyLearningRequestModel requestModel. + /// A representing the result of the asynchronous operation. + Task GetUserCertificateFromMoodleInstancesAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, string filterText = ""); + + /// + /// GetCoursesByCategoryIdAsync. + /// + /// The categoryId. + /// + Task GetCoursesByCategoryIdAsync(string categoryId); + + /// + /// GetSubCategoryByCategoryIdAsync. + /// + /// The categoryId. + /// + Task> GetSubCategoryByCategoryIdAsync(string categoryId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs index 04a68d86f..aabdc0d99 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMyLearningService.cs @@ -26,7 +26,7 @@ public interface IMyLearningService /// /// The user id. /// The request model. /// The . - Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningRequestModel requestModel); + Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningApiRequestViewModel requestModel); /// /// Gets history of users my leraning activities. @@ -34,7 +34,7 @@ public interface IMyLearningService /// /// The user id. /// The request model. /// The . - Task GetUserLearningHistoryAsync(int userId, MyLearningRequestModel requestModel); + Task GetUserLearningHistoryAsync(int userId, MyLearningApiRequestViewModel requestModel); /// /// Gets the played segment data for the progress modal in My Learning screen. @@ -69,6 +69,6 @@ public interface IMyLearningService /// The user id. /// The request model /// The . - Task GetUserCertificateDetails(int userId, MyLearningRequestModel requestModel); + Task GetUserCertificateDetails(int userId, MyLearningApiRequestViewModel requestModel); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/MoodleInstanceUsersHelper.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/MoodleInstanceUsersHelper.cs new file mode 100644 index 000000000..de3272ec0 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Helpers/MoodleInstanceUsersHelper.cs @@ -0,0 +1,47 @@ +namespace LearningHub.Nhs.OpenApi.Services.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Resource.Blocks; + + /// + /// A class containing helper functions for building moodle user instances. + /// + public class MoodleInstanceUsersHelper + { + /// + /// BuildMoodleUserInstances. + /// + /// + /// + public static MoodleInstanceUserIdsViewModel BuildUserIdsByInstance( + IEnumerable mappings) + { + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var m in mappings) + { + if (m?.User is null) continue; + if (m.User.Id <= 0) continue; + + var key = string.IsNullOrWhiteSpace(m.Instance) + ? $"instance_{m.User.Id}" + : m.Instance!; + + if (!dict.ContainsKey(key)) + { + dict[key] = m.User.Id; + } + } + + return new MoodleInstanceUserIdsViewModel + { + MoodleInstanceUserIds = dict + }; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleBridgeHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleBridgeHttpClient.cs new file mode 100644 index 000000000..e7812294d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleBridgeHttpClient.cs @@ -0,0 +1,97 @@ +using IdentityModel.Client; +using LearningHub.Nhs.Models.Entities.Reporting; +using LearningHub.Nhs.OpenApi.Models.Configuration; +using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Services.HttpClients +{ + /// + /// The Moodle Http Client. + /// + public class MoodleBridgeHttpClient : IMoodleBridgeHttpClient, IDisposable + { + private readonly MoodleBridgeAPIConfig moodleBridgeApiConfig; + private readonly HttpClient httpClient = new(); + + private bool initialised = false; + private string moodleBridgeApiUrl; + private string moodleBridgeApiToken; + + /// + /// Initializes a new instance of the class. + /// + /// httpClient. + /// config. + public MoodleBridgeHttpClient(HttpClient httpClient, IOptions moodleBridgeApiConfig) + { + this.moodleBridgeApiConfig = moodleBridgeApiConfig.Value; + this.httpClient = httpClient; + + this.moodleBridgeApiUrl = this.moodleBridgeApiConfig.BaseUrl; + this.moodleBridgeApiToken = this.moodleBridgeApiConfig.Token; + } + + /// + /// The Get Client method. + /// + /// The . + public async Task GetClient() + { + this.Initialise(this.moodleBridgeApiUrl); + return this.httpClient; + } + + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// The dispoase. + /// + /// disposing. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.httpClient.Dispose(); + } + } + + private void Initialise(string httpClientUrl) + { + if (this.initialised == false) + { + this.httpClient.BaseAddress = new Uri(httpClientUrl); + this.httpClient.DefaultRequestHeaders.Accept.Clear(); + this.httpClient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + this.httpClient.DefaultRequestHeaders.Add("X-API-KEY", moodleBridgeApiToken); + this.initialised = true; + } + } + + /// + public async Task GetData(string requestUrl, string? authHeader) + { + if (!string.IsNullOrEmpty(authHeader)) + { + this.httpClient.SetBearerToken(authHeader); + } + + var message = await this.httpClient.GetAsync(requestUrl).ConfigureAwait(false); + return message; + } + } +} 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 ae8d05738..b99a31373 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -31,7 +31,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 5edfe8cc4..0dfb757e7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -229,7 +229,11 @@ public async Task GetCatalogueAsync(int id) // Used by the admin screen to inform the admin user if they need to add a user group. vm.HasUserGroup = this.GetRoleUserGroupsForCatalogue(vm.NodeId).Any(); vm.Providers = await this.providerService.GetAllAsync(); - vm.SelectedCategoryId = await this.categoryService.GetByCatalogueVersionIdAsync(vm.CatalogueNodeVersionId); + var cataloguNodeeCategories = await this.categoryService.GetByCatalogueVersionIdAsync(vm.CatalogueNodeVersionId); + if (cataloguNodeeCategories != null) + { + vm.SelectedCategoryId = cataloguNodeeCategories.InstanceName + ":" + cataloguNodeeCategories.CategoryId; + } return vm; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CategoryService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CategoryService.cs index e950a1d1f..32b5dfdda 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CategoryService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CategoryService.cs @@ -36,10 +36,10 @@ public CategoryService(ICategoryRepository categoryRepository, IMapper mapper) } /// - public async Task GetByCatalogueVersionIdAsync(int nodeVersionId) + public async Task GetByCatalogueVersionIdAsync(int nodeVersionId) { var category = await categoryRepository.GetCategoryByCatalogueIdAsync(nodeVersionId); - return category != null ? category.CategoryId : 0; + return category; } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs index ad7868baf..5da2bc71b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -35,6 +35,11 @@ public class DashboardService : IDashboardService /// private readonly IMoodleApiService moodleApiService; + /// + /// The moodleBridgeApiService. + /// + private readonly IMoodleBridgeApiService moodleBridgeApiService; + /// /// The resourceActivityRepository. /// @@ -54,9 +59,10 @@ public class DashboardService : IDashboardService /// ratingService. /// providerService. /// moodleApiService. + /// moodleBridgeApiService. /// resourceActivityRepository. /// resourceRepository. - public DashboardService(IMapper mapper, IResourceVersionRepository resourceVersionRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, IRatingService ratingService, IProviderService providerService, IMoodleApiService moodleApiService, IResourceActivityRepository resourceActivityRepository, IResourceRepository resourceRepository) + public DashboardService(IMapper mapper, IResourceVersionRepository resourceVersionRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, IRatingService ratingService, IProviderService providerService, IMoodleApiService moodleApiService, IMoodleBridgeApiService moodleBridgeApiService, IResourceActivityRepository resourceActivityRepository, IResourceRepository resourceRepository) { this.mapper = mapper; this.resourceVersionRepository = resourceVersionRepository; @@ -66,6 +72,7 @@ public DashboardService(IMapper mapper, IResourceVersionRepository resourceVersi this.moodleApiService = moodleApiService; this.resourceActivityRepository = resourceActivityRepository; this.resourceRepository = resourceRepository; + this.moodleBridgeApiService = moodleBridgeApiService; } /// @@ -126,28 +133,28 @@ public async Task GetMyAccessLearnings(str /// The userId. /// The resourceType. /// A representing the result of the asynchronous operation. - public async Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, int pageNumber, int userId, string resourceType) + public async Task GetMyCoursesAndElearning(GetMyCoursesAndElearningRequestModel requestModel, int userId, string resourceType) { MyLearningActivitiesDetailedViewModel myInProgressActivities = new(); MyLearningCertificatesDetailedViewModel certificates = new(); List resources = new(); int resourceCount = 0; - if (dashboardType == "my-in-progress") + if (requestModel.DashboardType == "my-in-progress") { - myInProgressActivities = await this.GetMyInprogressLearningAsync(dashboardTrayLearningResourceType, pageNumber, userId); + myInProgressActivities = await this.GetMyInprogressLearningAsync(requestModel.DashboardTrayLearningResourceType, requestModel.PageNumber, userId, requestModel.Email); } - else if (dashboardType == "my-certificates") + else if (requestModel.DashboardType == "my-certificates") { - certificates = await this.GetUserCertificateDetailsAsync(dashboardTrayLearningResourceType, pageNumber, userId); + certificates = await this.GetUserCertificateDetailsAsync(requestModel.DashboardTrayLearningResourceType, requestModel.PageNumber, userId, requestModel.Email); } else { - var result = resourceVersionRepository.GetResources(dashboardType, pageNumber, userId); + var result = resourceVersionRepository.GetResources(requestModel.DashboardType, requestModel.PageNumber, userId); resourceCount = result.resourceCount; resources = result.resources ?? new List(); } - var cataloguesResponse = dashboardType.ToLower() == "my-catalogues" ? catalogueNodeVersionRepository.GetCatalogues(dashboardType, pageNumber, userId) : (TotalCount: 0, Catalogues: new List()); + var cataloguesResponse = requestModel.DashboardType.ToLower() == "my-catalogues" ? catalogueNodeVersionRepository.GetCatalogues(requestModel.DashboardType, requestModel.PageNumber, userId) : (TotalCount: 0, Catalogues: new List()); var catalogueList = cataloguesResponse.Catalogues.Any() ? mapper.Map>(cataloguesResponse.Catalogues) : new List(); if (catalogueList.Any()) @@ -196,19 +203,19 @@ public async Task GetMyCoursesAndElearning var response = new DashboardMyLearningResponseViewModel { - Type = dashboardType, + Type = requestModel.DashboardType, Resources = resourceList, Catalogues = catalogueList, Activities = myInProgressActivities.Activities, UserCertificates = certificates.Certificates, - TotalCount = dashboardType?.ToLower() switch + TotalCount = requestModel.DashboardType?.ToLower() switch { "my-catalogues" => cataloguesResponse.TotalCount, "my-in-progress" => myInProgressActivities?.TotalCount ?? 0, "my-certificates" => certificates?.TotalCount ?? 0, _ => resourceCount }, - CurrentPage = pageNumber, + CurrentPage = requestModel.PageNumber, }; return response; @@ -220,11 +227,12 @@ public async Task GetMyCoursesAndElearning /// The dashboardTrayLearningResourceType. /// The pageNumber. /// The user id. + /// The email. /// The . - public async Task GetMyInprogressLearningAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId) + public async Task GetMyInprogressLearningAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId, string email) { List result = new(); - List entrolledCourses = new(); + MoodleCompletionsApiResponseModel entrolledCourses = new(); List mappedMyLearningActivities = new(); List mappedEnrolledCourses = new(); if (dashboardTrayLearningResourceType != "courses") @@ -234,7 +242,7 @@ public async Task GetMyInprogressLearning if (dashboardTrayLearningResourceType != "elearning") { - entrolledCourses = await this.moodleApiService.GetInProgressEnrolledCoursesAsync(userId); + entrolledCourses = await this.moodleBridgeApiService.GetInProgressEnrolledCoursesAsync(email); } if (result != null) @@ -269,36 +277,39 @@ public async Task GetMyInprogressLearning if (entrolledCourses != null) { - mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombinedActivitiesViewModel - { - UserId = userId, - ResourceId = (int)course.Id, - ResourceVersionId = (int)course.Id, - IsCurrentResourceVersion = true, - ResourceReferenceId = (int)course.Id, - MajorVersion = 1, - MinorVersion = 0, - ResourceType = ResourceTypeEnum.Moodle, - Title = course.DisplayName, - CertificateEnabled = course.CertificateEnabled, - ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, - ActivityDate = course.LastAccessDate.HasValue + mappedEnrolledCourses = entrolledCourses?.Results? + .Where(r => r.Data?.Courses != null) + .SelectMany(r => r.Data.Courses) + .Select(course => new MyLearningCombinedActivitiesViewModel + { + UserId = userId, + ResourceId = (int)course.Id, + ResourceVersionId = (int)course.Id, + IsCurrentResourceVersion = true, + ResourceReferenceId = (int)course.Id, + MajorVersion = 1, + MinorVersion = 0, + ResourceType = ResourceTypeEnum.Moodle, + Title = course.DisplayName, + CertificateEnabled = course.CertificateEnabled, + ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, + ActivityDate = course.LastAccessDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.LastAccessDate.Value) : DateTimeOffset.MinValue, - ScorePercentage = Convert.ToInt32(course.ProgressPercentage.TrimEnd('%')), - TotalActivities = course.TotalActivities, - CompletedActivities = course.CompletedActivities, - IsMostRecent = false, - ResourceDurationMilliseconds = 0, - CompletionPercentage = 0, - ProvidersJson = null, - AssesmentScore = 0, - AssessmentPassMark = 0, - AssessmentType = 0, - CertificateAwardedDate = course.EndDate.HasValue + ScorePercentage = Convert.ToInt32(course.ProgressPercentage.TrimEnd('%')), + TotalActivities = course.TotalActivities, + CompletedActivities = course.CompletedActivities, + IsMostRecent = false, + ResourceDurationMilliseconds = 0, + CompletionPercentage = 0, + ProvidersJson = null, + AssesmentScore = 0, + AssessmentPassMark = 0, + AssessmentType = 0, + CertificateAwardedDate = course.EndDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) : DateTimeOffset.MinValue, - }).ToList(); + }).ToList(); } // Combine both result sets @@ -327,16 +338,16 @@ public async Task GetMyInprogressLearning /// The pageNumber. /// The user id. /// The . - public async Task GetUserCertificateDetailsAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId) + public async Task GetUserCertificateDetailsAsync(string dashboardTrayLearningResourceType, int pageNumber, int userId, string email) { try { - Task>? courseCertificatesTask = null; Task>? resourceCertificatesTask = null; + Task? courseCertificatesTask = null; if (dashboardTrayLearningResourceType != "elearning") { - courseCertificatesTask = moodleApiService.GetUserCertificateAsync(userId); + courseCertificatesTask = moodleBridgeApiService.GetUserCertificateAsync(email); } if (dashboardTrayLearningResourceType != "courses") @@ -361,23 +372,24 @@ public async Task GetUserCertificateDet if (courseCertificatesTask != null) { - var courseCertificates = courseCertificatesTask.Result ?? Enumerable.Empty(); - - mappedCourseCertificates = courseCertificates.Select(c => new UserCertificateViewModel - { - Title = string.IsNullOrWhiteSpace(c.ResourceTitle) ? c.ResourceName : c.ResourceTitle, - ResourceTypeId = (int)ResourceTypeEnum.Moodle, - ResourceReferenceId = 0, - MajorVersion = 0, - MinorVersion = 0, - AwardedDate = c.AwardedDate.HasValue + mappedCourseCertificates = courseCertificatesTask.Result?.Results? + .Where(r => r.Data?.Certificates != null) + .SelectMany(r => r.Data.Certificates) + .Select(c => new UserCertificateViewModel + { + Title = string.IsNullOrWhiteSpace(c.ResourceTitle) ? c.ResourceName : c.ResourceTitle, + ResourceTypeId = (int)ResourceTypeEnum.Moodle, + ResourceReferenceId = 0, + MajorVersion = 0, + MinorVersion = 0, + AwardedDate = c.AwardedDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(c.AwardedDate.Value) : DateTimeOffset.MinValue, - CertificatePreviewUrl = c.PreviewLink, - CertificateDownloadUrl = c.DownloadLink, - ResourceVersionId = 0, - ProvidersJson = null - }); + CertificatePreviewUrl = c.PreviewLink, + CertificateDownloadUrl = c.DownloadLink, + ResourceVersionId = 0, + ProvidersJson = null + }); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs index 9630ff24d..f868d946a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -43,7 +43,7 @@ public MoodleApiService(IMoodleHttpClient moodleHttpClient, ILoggerUserId from Moodle. public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) { - var parameters = new Dictionary + var parameters = new Dictionary { { "criteria[0][key]", "username" }, { "criteria[0][value]", currentUserId.ToString() } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs new file mode 100644 index 000000000..599569673 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs @@ -0,0 +1,584 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using LearningHub.Nhs.Models.Moodle; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Models.Report.ReportCreate; + using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Text; + using System.Text.Json; + using System.Threading.Tasks; + using LearningHub.Nhs.OpenApi.Services.Helpers; + using System.Net.Http.Json; + using static System.Net.WebRequestMethods; + using IdentityModel.Client; + using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + + /// + /// MoodleBridgeApiService. + /// + public class MoodleBridgeApiService : IMoodleBridgeApiService + { + private readonly IMoodleBridgeHttpClient moodleBridgeHttpClient; + private readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// moodleHttpClient. + /// logger. + public MoodleBridgeApiService(IMoodleBridgeHttpClient moodleBridgeHttpClient, ILogger logger) + { + this.moodleBridgeHttpClient = moodleBridgeHttpClient; + this.logger = logger; + } + + /// + /// GetUserInstancesByEmailAsync. + /// + /// The email. + /// UserId from Moodle. + public async Task GetUserInstancesByEmail(string email) + { + MoodleUserIdsResponseModel viewmodel = null; + + try + { + var client = await this.moodleBridgeHttpClient.GetClient(); + + var request = $"/api/v1/Users/{Uri.EscapeDataString(email)}/instance-ids"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + viewmodel = System.Text.Json.JsonSerializer.Deserialize(result, options); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + if (viewmodel?.MoodleUserIds != null) + { + return MoodleInstanceUsersHelper.BuildUserIdsByInstance(viewmodel.MoodleUserIds); + } + else + { + throw new Exception("Failed to retrieve Moodle user IDs."); + } + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while fetching user instances by email."); + throw; + } + } + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle instances user id. + /// MyLearningRequestModel. + /// The months. + /// A representing the result of the asynchronous operation. + public async Task GetRecentEnrolledCoursesAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, MyLearningRequestModel requestModel, int? month = null) + { + try + { + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + + if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) + { + moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; + } + + string statusFilter = string.Empty; ; + + if ((requestModel.Incomplete && requestModel.Complete) || (!requestModel.Incomplete && !requestModel.Complete)) + { + statusFilter = string.Empty; ; + } + else if (requestModel.Incomplete) + { + statusFilter = "inprogress"; + } + else + { + statusFilter = "completed"; + } + + var client = await this.moodleBridgeHttpClient.GetClient(); + + var queryParams = new List(); + + if (!string.IsNullOrWhiteSpace(statusFilter)) + queryParams.Add($"statusfilter={statusFilter}"); + + if (month.HasValue) + queryParams.Add($"months={month.Value}"); + + if (!string.IsNullOrWhiteSpace(requestModel?.SearchText)) + { + queryParams.Add($"search={Uri.EscapeDataString(requestModel.SearchText)}"); + } + + var queryString = queryParams.Any() + ? "?" + string.Join("&", queryParams) + : string.Empty; + + + var requestUri = $"api/v1/users/recent-courses{queryString}"; + + var response = await client.PostAsJsonAsync( + requestUri, + moodleUserInstanceUserIds).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + var result = System.Text.Json.JsonSerializer.Deserialize(json, options); + + return result ?? new MoodleCompletionsApiResponseModel(); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + throw new Exception($"Request failed with status code {response.StatusCode}"); + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while fetching user's recent learning activities "); + throw; + } + } + + /// + /// GetEnrolledCoursesAsync. + /// + /// Moodle Instances user id. + /// MyLearningRequestModel requestModel. + /// A representing the result of the asynchronous operation. + public async Task GetEnrolledCoursesHistoryAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, MyLearningRequestModel requestModel) + { + try + { + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + + if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) + { + moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; + } + + string statusFilter = string.Empty; ; + + if ((requestModel.Incomplete && requestModel.Complete) || (!requestModel.Incomplete && !requestModel.Complete)) + { + statusFilter = string.Empty; + } + else if (requestModel.Incomplete) + { + statusFilter = "inprogress"; + } + else + { + statusFilter = "completed"; + } + + var client = await this.moodleBridgeHttpClient.GetClient(); + // Build query string (optional params) + var queryParams = new List(); + queryParams.Add($"months=0"); + if (!string.IsNullOrWhiteSpace(statusFilter)) + queryParams.Add($"statusfilter={statusFilter}"); + + if (!string.IsNullOrWhiteSpace(requestModel?.SearchText)) + { + queryParams.Add($"search={Uri.EscapeDataString(requestModel.SearchText)}"); + } + + var queryString = queryParams.Any() + ? "?" + string.Join("&", queryParams) + : string.Empty; + + var requestUri = $"api/v1/users/recent-courses{queryString}"; + + var response = await client.PostAsJsonAsync( + requestUri, + moodleUserInstanceUserIds).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + var result = System.Text.Json.JsonSerializer.Deserialize(json, options); + + return result ?? new MoodleCompletionsApiResponseModel(); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + throw new Exception($"Request failed with status code {response.StatusCode}"); + } + catch (Exception ex) + { + return null; + } + } + + /// + /// GetInProgressEnrolledCoursesAsync. + /// + /// user email. + /// A representing the result of the asynchronous operation. + public async Task GetInProgressEnrolledCoursesAsync(string email) + { + try + { + email = "binon.yesudhas@nhs.net"; + var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) + { + moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; + } + + string statusFilter = "inprogress"; + + var client = await this.moodleBridgeHttpClient.GetClient(); + // Build query string (optional params) + var queryParams = new List(); + queryParams.Add($"months=0"); + if (statusFilter != null) + queryParams.Add($"statusfilter={statusFilter}"); + + var queryString = queryParams.Any() + ? "?" + string.Join("&", queryParams) + : string.Empty; + + var requestUri = $"api/v1/users/recent-courses{queryString}"; + + var response = await client.PostAsJsonAsync( + requestUri, + moodleUserInstanceUserIds).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + var result = System.Text.Json.JsonSerializer.Deserialize(json, options); + + return result ?? new MoodleCompletionsApiResponseModel(); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + throw new Exception($"Request failed with status code {response.StatusCode}"); + } + catch (Exception ex) + { + return null; + } + } + + /// + /// GetUserLearningHistory. + /// + /// user email. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task GetUserCertificateAsync(string email, string filterText = "") + { + try + { + email = "binon.yesudhas@nhs.net"; + var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) + { + moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; + } + var client = await this.moodleBridgeHttpClient.GetClient(); + // Build query string (optional params) + var queryParams = new List(); + if (!string.IsNullOrWhiteSpace(filterText)) + { + queryParams.Add($"searchterm={Uri.EscapeDataString(filterText)}"); + } + + var queryString = queryParams.Any() + ? "?" + string.Join("&", queryParams) + : string.Empty; + + + var requestUri = $"api/v1/Users/certificates{queryString}"; + + var response = await client.PostAsJsonAsync( + requestUri, + moodleUserInstanceUserIds).ConfigureAwait(false); + + + if (response.IsSuccessStatusCode) + { + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + var result = await response.Content + .ReadFromJsonAsync(options) + .ConfigureAwait(false); + + return result ?? new MoodleCertificateResponseModel(); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + throw new Exception($"Request failed with status code {response.StatusCode}"); + } + catch (Exception ex) + { + return null; + } + } + + /// + /// GetUserCertificatefromMoodleInstancesAsync. + /// + /// moodleUserInstanceUserIds. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task GetUserCertificateFromMoodleInstancesAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, string filterText = "") + { + try + { + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) + { + moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; + } + var client = await this.moodleBridgeHttpClient.GetClient(); + + var queryParams = new List(); + if (!string.IsNullOrWhiteSpace(filterText)) + { + queryParams.Add($"searchterm={Uri.EscapeDataString(filterText)}"); + } + + var queryString = queryParams.Any() + ? "?" + string.Join("&", queryParams) + : string.Empty; + + + var requestUri = $"api/v1/Users/certificates{queryString}"; + + var response = await client.PostAsJsonAsync( + requestUri, + moodleUserInstanceUserIds).ConfigureAwait(false); + + + if (response.IsSuccessStatusCode) + { + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + var result = await response.Content + .ReadFromJsonAsync(options) + .ConfigureAwait(false); + + return result ?? new MoodleCertificateResponseModel(); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + throw new Exception($"Request failed with status code {response.StatusCode}"); + } + catch (Exception ex) + { + return null; + } + } + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + public async Task> GetAllMoodleCategoriesAsync() + { + MoodleInstancesCategoriesResponseModel viewmodel = null; + + try + { + var client = await this.moodleBridgeHttpClient.GetClient(); + + var request = $"/api/v1/Courses/categories"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + viewmodel = System.Text.Json.JsonSerializer.Deserialize(result, options); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel.Results; + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while fetching user instances by email."); + throw; // Re-throw the exception to ensure the caller is aware of the failure + } + } + + /// + /// GetCoursesByCategoryIdAsync. + /// + /// The categoryId. + /// + public async Task GetCoursesByCategoryIdAsync(string selectedcategoryId) + { + MoodleCourseResultsResponseModel viewmodel = null; + var (instanceName, categoryId) = selectedcategoryId.Split(':') is var p && p.Length == 2 ? (p[0], p[1]) : (null, null); + + try + { + var client = await this.moodleBridgeHttpClient.GetClient(); + + var request = $"api/v1/Courses/search?value={Uri.EscapeDataString(categoryId.ToString())}" + $"&instance={Uri.EscapeDataString(instanceName)}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + viewmodel = System.Text.Json.JsonSerializer + .Deserialize(result, options); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel ?? new MoodleCourseResultsResponseModel(); + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while fetching Moodle categories."); + throw; + } + } + + /// + /// GetSubCategoryByCategoryIdAsync. + /// + /// The categoryId. + /// + public async Task> GetSubCategoryByCategoryIdAsync(string selectedcategoryId) + { + MoodleInstanceSubCategoryResponseModel subcategories = null; + var (instanceName, categoryId) = selectedcategoryId.Split(':') is var p && p.Length == 2 ? (p[0], p[1]) : (null, null); + + try + { + var client = await this.moodleBridgeHttpClient.GetClient(); + + var request = $"api/v1/Courses/{categoryId}/subcategories?{instanceName}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + subcategories = System.Text.Json.JsonSerializer.Deserialize(result, options); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return subcategories.Results; + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while fetching sub categories by category id."); + throw; + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index d9c124b89..12d9e42cd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -12,8 +12,10 @@ using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Models.Resource.AzureMediaAsset; using LearningHub.Nhs.OpenApi.Models.Configuration; using LearningHub.Nhs.OpenApi.Repositories.Helpers; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; @@ -75,6 +77,11 @@ public class MyLearningService : IMyLearningService /// private readonly IMoodleApiService moodleApiService; + /// + /// The moodleBridgeApiService. + /// + private readonly IMoodleBridgeApiService moodleBridgeApiService; + /// /// The mapper. /// @@ -99,6 +106,7 @@ public class MyLearningService : IMyLearningService /// The mediaResourceActivity. /// The resourceActivity /// The moodleApiService. + /// The moodleBridgeApiService. public MyLearningService( IResourceActivityRepository resourceActivityRepository, IMediaResourcePlayedSegmentRepository mediaResourcePlayedSegmentRepository, @@ -110,7 +118,8 @@ public MyLearningService( IScormActivityRepository scormActivityRepository, IMediaResourceActivityRepository mediaResourceActivity, IResourceRepository resourceRepository, - IMoodleApiService moodleApiService) + IMoodleApiService moodleApiService, + IMoodleBridgeApiService moodleBridgeApiService) { this.resourceActivityRepository = resourceActivityRepository; this.mediaResourcePlayedSegmentRepository = mediaResourcePlayedSegmentRepository; @@ -123,6 +132,7 @@ public MyLearningService( this.mediaResourceActivity = mediaResourceActivity; this.resourceRepository = resourceRepository; this.moodleApiService = moodleApiService; + this.moodleBridgeApiService = moodleBridgeApiService; } /// @@ -155,13 +165,12 @@ public async Task GetActivityDetailed(int userId, M /// /// The user id. /// The request model. /// The . - public async Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningRequestModel requestModel) + public async Task GetUserRecentMyLearningActivitiesAsync(int userId, MyLearningApiRequestViewModel requestModel) { try { - var result = await resourceActivityRepository.GetUserRecentMyLearningActivities(userId, requestModel); - - var entrolledCourses = await this.moodleApiService.GetRecentEnrolledCoursesAsync(userId, requestModel, 6); + var result = await resourceActivityRepository.GetUserRecentMyLearningActivities(userId, requestModel.Request); + var entrolledCourses = await this.moodleBridgeApiService.GetRecentEnrolledCoursesAsync(requestModel.MoodleInstanceUserIds, requestModel.Request, 6); List mappedMyLearningActivities = new(); List mappedEnrolledCourses = new(); List combainedUserActivities = new(); @@ -208,43 +217,46 @@ public async Task GetUserRecentMyLearning if (entrolledCourses != null) { - mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombinedActivitiesViewModel - { - UserId = userId, - ResourceId = (int)course.Id, - ResourceVersionId = (int)course.Id, - IsCurrentResourceVersion = true, - ResourceReferenceId = (int)course.Id, - MajorVersion = 1, - MinorVersion = 0, - ResourceType = ResourceTypeEnum.Moodle, - Title = course.DisplayName, - CertificateEnabled = course.CertificateEnabled, - ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, - ActivityDate = course.LastAccessDate.HasValue - ? DateTimeOffset.FromUnixTimeSeconds(course.LastAccessDate.Value) - : DateTimeOffset.MinValue, - ScorePercentage = Convert.ToInt32(course.ProgressPercentage.TrimEnd('%')), - TotalActivities = course.TotalActivities, - CompletedActivities = course.CompletedActivities, - IsMostRecent = false, - ResourceDurationMilliseconds = 0, - CompletionPercentage = 0, - ProvidersJson = null, - AssessmentPassMark = 0, - AssessmentType = 0, - AssesmentScore = 0, - CertificateAwardedDate = course.EndDate.HasValue - ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) - : DateTimeOffset.MinValue, - }).ToList(); + mappedEnrolledCourses = entrolledCourses?.Results? + .Where(r => r.Data?.Courses != null) + .SelectMany(r => r.Data.Courses) + .Select(course => new MyLearningCombinedActivitiesViewModel + { + UserId = userId, + ResourceId = (int)course.Id, + ResourceVersionId = (int)course.Id, + IsCurrentResourceVersion = true, + ResourceReferenceId = (int)course.Id, + MajorVersion = 1, + MinorVersion = 0, + ResourceType = ResourceTypeEnum.Moodle, + Title = course.DisplayName, + CertificateEnabled = course.CertificateEnabled, + ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, + ActivityDate = course.LastAccessDate.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(course.LastAccessDate.Value) + : DateTimeOffset.MinValue, + ScorePercentage = Convert.ToInt32(course.ProgressPercentage.TrimEnd('%')), + TotalActivities = course.TotalActivities, + CompletedActivities = course.CompletedActivities, + IsMostRecent = false, + ResourceDurationMilliseconds = 0, + CompletionPercentage = 0, + ProvidersJson = null, + AssessmentPassMark = 0, + AssessmentType = 0, + AssesmentScore = 0, + CertificateAwardedDate = course.EndDate.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) + : DateTimeOffset.MinValue, + }).ToList(); } // Combine both result sets combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); - var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Skip).Take(requestModel.Take).ToList(); + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Request.Skip).Take(requestModel.Request.Take).ToList(); // Count total records. MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() @@ -268,29 +280,29 @@ public async Task GetUserRecentMyLearning /// /// The user id. /// The request model. /// The . - public async Task GetUserLearningHistoryAsync(int userId, MyLearningRequestModel requestModel) + public async Task GetUserLearningHistoryAsync(int userId, MyLearningApiRequestViewModel requestModel) { try { - (string strActivityStatus, bool activityStatusEnumFlag) = resourceActivityRepository.GetActivityStatusFilter(requestModel); - (string strResourceTypes, bool resourceTypeFlag) = resourceActivityRepository.ApplyResourceTypesfilters(requestModel); + (string strActivityStatus, bool activityStatusEnumFlag) = resourceActivityRepository.GetActivityStatusFilter(requestModel.Request); + (string strResourceTypes, bool resourceTypeFlag) = resourceActivityRepository.ApplyResourceTypesfilters(requestModel.Request); var result = new List(); if ( - (!activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Courses) || - (activityStatusEnumFlag && resourceTypeFlag && !requestModel.Courses) || - (activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Courses) || - (!activityStatusEnumFlag && resourceTypeFlag && !requestModel.Courses) || - (!activityStatusEnumFlag && resourceTypeFlag && requestModel.Courses) || - (activityStatusEnumFlag && resourceTypeFlag && requestModel.Courses)) + (!activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Request.Courses) || + (activityStatusEnumFlag && resourceTypeFlag && !requestModel.Request.Courses) || + (activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Request.Courses) || + (!activityStatusEnumFlag && resourceTypeFlag && !requestModel.Request.Courses) || + (!activityStatusEnumFlag && resourceTypeFlag && requestModel.Request.Courses) || + (activityStatusEnumFlag && resourceTypeFlag && requestModel.Request.Courses)) { - if (requestModel.SearchText != null) + if (requestModel.Request.SearchText != null) { - result = await resourceActivityRepository.GetUserLearningHistoryBasedonSearchText(userId, requestModel); + result = await resourceActivityRepository.GetUserLearningHistoryBasedonSearchText(userId, requestModel.Request); } else { - result = await resourceActivityRepository.GetUserLearningHistory(userId, requestModel); + result = await resourceActivityRepository.GetUserLearningHistory(userId, requestModel.Request); } } @@ -338,56 +350,60 @@ public async Task GetUserLearningHistoryA }).ToList(); } - List entrolledCourses = new(); + MoodleCompletionsApiResponseModel entrolledCourses = new(); if ( - (!activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Courses) || - (!activityStatusEnumFlag && !resourceTypeFlag && requestModel.Courses) || - (!activityStatusEnumFlag && resourceTypeFlag && requestModel.Courses) || - (activityStatusEnumFlag && resourceTypeFlag && requestModel.Courses) || - (activityStatusEnumFlag && !resourceTypeFlag && requestModel.Courses) || - (activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Courses)) + (!activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Request.Courses) || + (!activityStatusEnumFlag && !resourceTypeFlag && requestModel.Request.Courses) || + (!activityStatusEnumFlag && resourceTypeFlag && requestModel.Request.Courses) || + (activityStatusEnumFlag && resourceTypeFlag && requestModel.Request.Courses) || + (activityStatusEnumFlag && !resourceTypeFlag && requestModel.Request.Courses) || + (activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Request.Courses)) { - entrolledCourses = await this.moodleApiService.GetEnrolledCoursesHistoryAsync(userId, requestModel); + ////entrolledCourses = await this.moodleApiService.GetEnrolledCoursesHistoryAsync(userId, requestModel.Request); + entrolledCourses = await this.moodleBridgeApiService.GetEnrolledCoursesHistoryAsync(requestModel.MoodleInstanceUserIds, requestModel.Request); if (entrolledCourses != null) { - mappedEnrolledCourses = entrolledCourses.Select(course => new MyLearningCombinedActivitiesViewModel - { - UserId = userId, - ResourceId = (int)course.Id, - ResourceVersionId = (int)course.Id, - IsCurrentResourceVersion = true, - ResourceReferenceId = (int)course.Id, - MajorVersion = 1, - MinorVersion = 0, - ResourceType = ResourceTypeEnum.Moodle, - Title = course.DisplayName, - CertificateEnabled = course.CertificateEnabled, - ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, - ActivityDate = course.LastAccessDate.HasValue + mappedEnrolledCourses = entrolledCourses?.Results? + .Where(r => r.Data?.Courses != null) + .SelectMany(r => r.Data.Courses) + .Select(course => new MyLearningCombinedActivitiesViewModel + { + UserId = userId, + ResourceId = (int)course.Id, + ResourceVersionId = (int)course.Id, + IsCurrentResourceVersion = true, + ResourceReferenceId = (int)course.Id, + MajorVersion = 1, + MinorVersion = 0, + ResourceType = ResourceTypeEnum.Moodle, + Title = course.DisplayName, + CertificateEnabled = course.CertificateEnabled, + ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, + ActivityDate = course.LastAccessDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.LastAccessDate.Value) : DateTimeOffset.MinValue, - ScorePercentage = int.TryParse(course.ProgressPercentage.TrimEnd('%'), out var score) ? score : 0, - TotalActivities = course.TotalActivities, - CompletedActivities = course.CompletedActivities, - IsMostRecent = false, - ResourceDurationMilliseconds = 0, - CompletionPercentage = 0, - ProvidersJson = null, - AssessmentPassMark = 0, - AssessmentType = 0, - AssesmentScore = 0, - CertificateAwardedDate = course.EndDate.HasValue + ScorePercentage = int.TryParse(course.ProgressPercentage.TrimEnd('%'), out var score) ? score : 0, + TotalActivities = course.TotalActivities, + CompletedActivities = course.CompletedActivities, + IsMostRecent = false, + ResourceDurationMilliseconds = 0, + CompletionPercentage = 0, + ProvidersJson = null, + AssessmentPassMark = 0, + AssessmentType = 0, + AssesmentScore = 0, + CertificateAwardedDate = course.EndDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) : DateTimeOffset.MinValue, - }).ToList(); + }).ToList(); } } // Combine both result sets combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); - var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Skip).Take(requestModel.Take).ToList(); + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(requestModel.Request.Skip).Take(requestModel.Request.Take).ToList(); // Count total records. MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() @@ -702,20 +718,19 @@ public async Task> PopulateMyLearningDetai /// The user id. /// The request model /// The . - public async Task GetUserCertificateDetails(int userId, MyLearningRequestModel requestModel) + public async Task GetUserCertificateDetails(int userId, MyLearningApiRequestViewModel requestModel) { - Task>? courseCertificatesTask = null; - var filteredResource = GetFilteredResourceType(requestModel); + Task? courseCertificatesTask = null; + var filteredResource = GetFilteredResourceType(requestModel.Request); - if (filteredResource.Count() == 0 || (filteredResource.Any() && requestModel.Courses)) + if (filteredResource.Count() == 0 || (filteredResource.Any() && requestModel.Request.Courses)) { - courseCertificatesTask = !string.IsNullOrWhiteSpace(requestModel.SearchText) ? - moodleApiService.GetUserCertificateAsync(userId, requestModel.SearchText) : moodleApiService.GetUserCertificateAsync(userId); - + courseCertificatesTask = !string.IsNullOrWhiteSpace(requestModel.Request.SearchText) ? + moodleBridgeApiService.GetUserCertificateFromMoodleInstancesAsync(requestModel.MoodleInstanceUserIds, requestModel.Request.SearchText) : moodleBridgeApiService.GetUserCertificateFromMoodleInstancesAsync(requestModel.MoodleInstanceUserIds); } - var resourceCertificatesTask = !string.IsNullOrWhiteSpace(requestModel.SearchText) ? - resourceRepository.GetUserCertificateDetails(userId, requestModel.SearchText) : resourceRepository.GetUserCertificateDetails(userId); + var resourceCertificatesTask = !string.IsNullOrWhiteSpace(requestModel.Request.SearchText) ? + resourceRepository.GetUserCertificateDetails(userId, requestModel.Request.SearchText) : resourceRepository.GetUserCertificateDetails(userId); // Await all active tasks in parallel @@ -730,23 +745,24 @@ public async Task GetUserCertificateDet if (courseCertificatesTask != null) { - var courseCertificates = courseCertificatesTask.Result ?? Enumerable.Empty(); - - mappedCourseCertificates = courseCertificates.Select(c => new UserCertificateViewModel - { - Title = string.IsNullOrWhiteSpace(c.ResourceTitle) ? c.ResourceName : c.ResourceTitle, - ResourceTypeId = (int)ResourceTypeEnum.Moodle, - ResourceReferenceId = 0, - MajorVersion = 0, - MinorVersion = 0, - AwardedDate = c.AwardedDate.HasValue + mappedCourseCertificates = courseCertificatesTask.Result?.Results? + .Where(r => r.Data?.Certificates != null) + .SelectMany(r => r.Data.Certificates) + .Select(c => new UserCertificateViewModel + { + Title = string.IsNullOrWhiteSpace(c.ResourceTitle) ? c.ResourceName : c.ResourceTitle, + ResourceTypeId = (int)ResourceTypeEnum.Moodle, + ResourceReferenceId = 0, + MajorVersion = 0, + MinorVersion = 0, + AwardedDate = c.AwardedDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(c.AwardedDate.Value) : DateTimeOffset.MinValue, - CertificatePreviewUrl = c.PreviewLink, - CertificateDownloadUrl = c.DownloadLink, - ResourceVersionId = 0, - ProvidersJson = null, - }); + CertificatePreviewUrl = c.PreviewLink, + CertificateDownloadUrl = c.DownloadLink, + ResourceVersionId = 0, + ProvidersJson = null, + }); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); @@ -764,8 +780,8 @@ public async Task GetUserCertificateDet var totalCount = orderedCertificates.Count(); var pagedResults = orderedCertificates - .Skip(requestModel.Skip) - .Take(requestModel.Take) + .Skip(requestModel.Request.Skip) + .Take(requestModel.Request.Take) .ToList(); return new MyLearningCertificatesDetailedViewModel @@ -775,9 +791,6 @@ public async Task GetUserCertificateDet }; } - - - private IQueryable ApplyFilters(IQueryable query, MyLearningRequestModel requestModel) { // Text filter - Title, Keywords or Description. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index 73c887df7..1670bc090 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -39,6 +39,7 @@ public static void AddServices(this IServiceCollection services, IConfiguration } services.AddHttpClient(); + services.AddHttpClient(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -90,6 +91,7 @@ public static void AddServices(this IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); // Register IFindwiseApiFacade based on feature flag 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 93461ce2b..27ab96576 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs index 309eb8bd5..07c0e431b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs @@ -46,10 +46,16 @@ public static class ConfigurationExtensions public const string AzureSectionName = "Azure"; /// - /// The FindwiseSectionName. + /// The MoodleSectionName. /// public const string MoodleSectionName = "Moodle"; + /// + /// The MoodleBridgeSectionName. + /// + public const string MoodleBridgeSectionName = "MoodleBridgeAPIConfig"; + + /// /// The DatabricksSectionName. /// @@ -83,6 +89,8 @@ public static void AddConfig(this IServiceCollection services, IConfiguration co services.AddOptions().Bind(config.GetSection(MoodleSectionName)); + services.AddOptions().Bind(config.GetSection(MoodleBridgeSectionName)); + services.AddOptions().Bind(config.GetSection(DatabricksSectionName)); services.AddOptions().Bind(config.GetSection(FeatureFlagsSectionName)); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CategoryController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CategoryController.cs index cc4b65562..f0e11089e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CategoryController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/CategoryController.cs @@ -19,16 +19,19 @@ public class CategoryController : OpenApiControllerBase { private readonly ICategoryService categoryService; private readonly IMoodleApiService moodleApiService; + private readonly IMoodleBridgeApiService moodleBridgeApiService; /// /// Initializes a new instance of the class. /// /// The category service. /// The moodleApi service. - public CategoryController(ICategoryService categoryService, IMoodleApiService moodleApiService) + /// The moodle Bridge Api service. + public CategoryController(ICategoryService categoryService, IMoodleApiService moodleApiService, IMoodleBridgeApiService moodleBridgeApiService) { this.categoryService = categoryService; this.moodleApiService = moodleApiService; + this.moodleBridgeApiService = moodleBridgeApiService; } /// @@ -51,9 +54,9 @@ public async Task GetCatalogueVersionCategory(int catalogueNodeVe /// The catalogue. [HttpGet] [Route("GetCoursesByCategoryId/{categoryId}")] - public async Task GetCoursesByCategoryId(int categoryId) + public async Task GetCoursesByCategoryId(string categoryId) { - var courses = await this.moodleApiService.GetCoursesByCategoryIdAsync(categoryId); + var courses = await this.moodleBridgeApiService.GetCoursesByCategoryIdAsync(categoryId); return this.Ok(courses); } @@ -64,9 +67,9 @@ public async Task GetCoursesByCategoryId(int categoryId) /// The catalogue. [HttpGet] [Route("GetSubCategoryByCategoryId/{categoryId}")] - public async Task GetSubCategoryByCategoryId(int categoryId) + public async Task GetSubCategoryByCategoryId(string categoryId) { - var subCategories = await this.moodleApiService.GetSubCategoryByCategoryIdAsync(categoryId); + var subCategories = await this.moodleBridgeApiService.GetSubCategoryByCategoryIdAsync(categoryId); return this.Ok(subCategories); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs index 987904d94..6131a92ec 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/DashboardController.cs @@ -1,6 +1,7 @@ namespace LearningHub.NHS.OpenAPI.Controllers { using System.Threading.Tasks; + using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -37,7 +38,7 @@ public DashboardController(IUserService userService, IDashboardService dashboard [Route("resources/{dashboardType}/{pageNumber}")] public async Task GetResources(string dashboardType, int pageNumber = 1) { - var response = await dashboardService.GetResources(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + var response = await this.dashboardService.GetResources(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); return this.Ok(response); } @@ -51,7 +52,7 @@ public async Task GetResources(string dashboardType, int pageNumbe [Route("catalogues/{dashboardType}/{pageNumber}")] public async Task GetCatalogues(string dashboardType, int pageNumber = 1) { - var response = await dashboardService.GetCatalogues(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + var response = await this.dashboardService.GetCatalogues(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); return this.Ok(response); } @@ -65,22 +66,20 @@ public async Task GetCatalogues(string dashboardType, int pageNum [Route("myaccesslearning/{dashboardType}/{pageNumber}")] public async Task GetMyAccessLearnings(string dashboardType, int pageNumber = 1) { - var response = await dashboardService.GetMyAccessLearnings(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); + var response = await this.dashboardService.GetMyAccessLearnings(dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault()); return this.Ok(response); } /// /// Gets Catalogues. /// - /// The dashboardTrayLearningResource type. - /// The dashboard type. - /// The page Number. + /// The GetMyCoursesAndElearningRequestModel. /// IActionResult. - [HttpGet] - [Route("GetMyCoursesAndElearning/{dashboardTrayLearningResourceType}/{dashboardType}/{pageNumber}")] - public async Task GetMyCoursesAndElearning(string dashboardTrayLearningResourceType, string dashboardType, int pageNumber = 1) + [HttpPost] + [Route("GetMyCoursesAndElearning")] + public async Task GetMyCoursesAndElearning([FromBody] GetMyCoursesAndElearningRequestModel request) { - var response = await dashboardService.GetMyCoursesAndElearning(dashboardTrayLearningResourceType, dashboardType, pageNumber, this.CurrentUserId.GetValueOrDefault(), "All"); + var response = await this.dashboardService.GetMyCoursesAndElearning(request, this.CurrentUserId.GetValueOrDefault(), "All"); return this.Ok(response); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs new file mode 100644 index 000000000..eb39f11cd --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs @@ -0,0 +1,51 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using System.Threading.Tasks; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + /// + /// Moodle Bridge operations. + /// + [Route("MoodleBridge")] + [ApiController] + [Authorize] + public class MoodleBridgeController : Controller + { + private readonly IMoodleBridgeApiService moodleBridgeApiService; + + /// + /// Initializes a new instance of the class. + /// + /// The moodle bridge service. + public MoodleBridgeController(IMoodleBridgeApiService moodleBridgeApiService) + { + this.moodleBridgeApiService = moodleBridgeApiService; + } + + /// + /// The GetMoodle Instances UserIds. + /// + /// The LH user email. + /// The . + [HttpGet] + [Route("GetUserInstancesByEmail/{email}")] + public async Task GetUserInstancesByEmail(string email) + { + var moodleUser = await this.moodleBridgeApiService.GetUserInstancesByEmail(email); + return this.Ok(moodleUser); + } + + /// + /// GetAllMoodleCategoriesAsync. + /// + /// A representing the result of the asynchronous operation. + [HttpGet] + [Route("GetAllMoodleCategories")] + public async Task GetAllMoodleCategoriesAsync() + { + var moodleCategories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + return this.Ok(moodleCategories); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs index 4763f21cc..fbd05a867 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MyLearningController.cs @@ -59,7 +59,7 @@ public async Task GetActivityDetailed([FromBody] MyLearningReques /// The . [HttpPost] [Route("GetUserRecentMyLearningActivities")] - public async Task GetUserRecentMyLearningActivities([FromBody] MyLearningRequestModel requestModel) + public async Task GetUserRecentMyLearningActivities([FromBody] MyLearningApiRequestViewModel requestModel) { var activityModel = await this.myLearningService.GetUserRecentMyLearningActivitiesAsync(this.CurrentUserId.GetValueOrDefault(), requestModel); return this.Ok(activityModel); @@ -72,7 +72,7 @@ public async Task GetUserRecentMyLearningActivities([FromBody] My /// The . [HttpPost] [Route("GetUserLearningHistory")] - public async Task GetUserLearningHistory([FromBody] MyLearningRequestModel requestModel) + public async Task GetUserLearningHistory([FromBody] MyLearningApiRequestViewModel requestModel) { var activityModel = await this.myLearningService.GetUserLearningHistoryAsync(this.CurrentUserId.GetValueOrDefault(), requestModel); return this.Ok(activityModel); @@ -118,7 +118,7 @@ public async Task GetResourceCertificateDetails(int resourceRefer /// A representing the result of the asynchronous operation. [HttpPost] [Route("GetUserCertificateDetails")] - public async Task GetUserCertificateDetails([FromBody] MyLearningRequestModel requestModel) + public async Task GetUserCertificateDetails([FromBody] MyLearningApiRequestViewModel requestModel) { var certificateDetails = await this.myLearningService.GetUserCertificateDetails(this.CurrentUserId.GetValueOrDefault(), requestModel); return this.Ok(certificateDetails); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index de5b7a6d7..57eb26f4f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -19,7 +19,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 42b84d3b8..3a96c27b9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -156,6 +156,10 @@ "ApiWsRestFormat": "json", "ApiWsToken": "" }, + "MoodleBridgeAPIConfig": { + "BaseUrl": "", + "Token": "" + }, "Databricks": { "InstanceUrl": "", "Token": "", 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 2a259d6b2..79f6af7f9 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 @@ -16,7 +16,7 @@ - + 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 aebb8bb75..c8ca7a2ae 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 @@ -18,7 +18,7 @@ - + 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 acfb75739..89bca5ef2 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + 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 9d86c24a0..ae1444ed9 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index f31ff35e5..c19afef98 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 3a7660599..0462105ff 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + 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 e1b7896b8..8d695c492 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + 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 4acbd2e13..100e6600d 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/CatalogueNodeVersionCategoryCreate.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/CatalogueNodeVersionCategoryCreate.sql index cd0fa408f..9818d8f75 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/CatalogueNodeVersionCategoryCreate.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/CatalogueNodeVersionCategoryCreate.sql @@ -6,12 +6,14 @@ -- Modification History -- -- 14-10-2025 SA Initial Revision. +-- 11-03-2026 SA Added instance name ------------------------------------------------------------------------------- CREATE PROCEDURE [hierarchy].[CatalogueNodeVersionCategoryCreate] ( @userId INT, @CatalogueNodeVersionId INT, - @CategoryId INT, + @categoryId INT, + @instanceName VARCHAR(50), @UserTimezoneOffset int = NULL ) @@ -33,19 +35,21 @@ BEGIN END INSERT INTO [hierarchy].[CatalogueNodeVersionCategory] - ([CatalogueNodeVersionId] - ,[CategoryId] - ,[Deleted] - ,[CreateUserId] - ,[CreateDate] - ,[AmendUserId] - ,[AmendDate]) - VALUES - (@CatalogueNodeVersionId - ,@CategoryId - ,0 - ,@userId - ,@AmendDate - ,@userId - ,@AmendDate) + ([CatalogueNodeVersionId] + ,[CategoryId] + ,[InstanceName] + ,[Deleted] + ,[CreateUserId] + ,[CreateDate] + ,[AmendUserId] + ,[AmendDate]) + VALUES + (@CatalogueNodeVersionId + ,@categoryId + ,@instanceName + ,0 + ,@userId + ,@AmendDate + ,@userId + ,@AmendDate) END \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Hierarchy/CatalogueNodeVersionCategory.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Hierarchy/CatalogueNodeVersionCategory.sql index c3ddc4832..b5c9f3f15 100644 --- a/WebAPI/LearningHub.Nhs.Database/Tables/Hierarchy/CatalogueNodeVersionCategory.sql +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Hierarchy/CatalogueNodeVersionCategory.sql @@ -2,6 +2,7 @@ [Id] [int] IDENTITY(1,1) NOT NULL, [CatalogueNodeVersionId] [int] NOT NULL, [CategoryId] [int] NOT NULL, + [InstanceName] [varchar](50) NULL, [Deleted] [bit] NOT NULL, [CreateUserId] [int] NOT NULL, [CreateDate] [datetimeoffset](7) NOT NULL, 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 5d12c4600..8273e219f 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index b7cca29dc..a2628d76c 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + 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 6ee630341..73e99b72e 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all 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 dbb1f6a30..6b02d693a 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 98cec165a..d27db0e89 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + 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 3a2cb8961..1a8367ea1 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 @@ -25,7 +25,7 @@ - + all 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 b275eae94..56c3518c1 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 @@ -9,7 +9,7 @@ - + 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 a18eb4196..e4a707d63 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 @@ -10,7 +10,7 @@ - + all 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 bb23ca661..a6d13b605 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 @@ -9,7 +9,7 @@ - + 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 82a9b4aa6..2e25b373f 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 @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index de745363e..e21f1b8e1 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From cce3e9dbcce5c8f10500877142674f0eba1fb533 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 19 Mar 2026 09:54:14 +0000 Subject: [PATCH 02/24] Removed commented lines and unused files --- .../Configuration/MoodleBridgeApiConfig.cs | 33 ------------------- .../Controllers/BaseController.cs | 3 +- .../Services/IMoodleBridgeApiService.cs | 2 +- .../Services/MoodleBridgeApiService.cs | 26 +-------------- .../Controllers/MoodleBridgeController.cs | 1 + 5 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs diff --git a/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs b/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs deleted file mode 100644 index 2e7e4b566..000000000 --- a/LearningHub.Nhs.WebUI/Configuration/MoodleBridgeApiConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Configuration -{ - /// - /// The Moodle Bridge Settings. - /// - public class MoodleBridgeApiConfig - { - /////// - /////// Gets or sets the base url for the Moodle service. - /////// - ////public string BaseUrl { get; set; } = null!; - - /////// - /////// Gets or sets the Web service Rest Format. - /////// - ////public string MoodleWSRestFormat { get; set; } = null!; - - /////// - /////// Gets or sets the token. - /////// - ////public string WSToken { get; set; } = null!; - - /////// - /////// Gets or sets the token. - /////// - ////public string ApiPath { get; set; } = "webservice/rest/server.php"; - - /////// - /////// Gets or sets the token. - /////// - ////public string CoursePath { get; set; } = "course/view.php"; - } -} diff --git a/LearningHub.Nhs.WebUI/Controllers/BaseController.cs b/LearningHub.Nhs.WebUI/Controllers/BaseController.cs index dd72350ec..77fd780ad 100644 --- a/LearningHub.Nhs.WebUI/Controllers/BaseController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/BaseController.cs @@ -126,8 +126,7 @@ public async Task GetUserIdsPerInstances() { if (this.moodleUserIds == null) { - ////var moodleUserInstances = await this.moodleBridgeApiService.GetUserInstancesByEmail(this.CurrentUserEmail); - var moodleUserInstances = await this.moodleBridgeApiService.GetUserInstancesByEmail("binon.yesudhas@nhs.net"); + var moodleUserInstances = await this.moodleBridgeApiService.GetUserInstancesByEmail(this.CurrentUserEmail); this.moodleUserIds = moodleUserInstances; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs index e099e8bb9..a3aa57b0d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs @@ -61,7 +61,7 @@ public interface IMoodleBridgeApiService /// GetUserCertificateFromMoodleInstancesAsync. /// /// Moodle Instances user id. - /// MyLearningRequestModel requestModel. + /// filterText. /// A representing the result of the asynchronous operation. Task GetUserCertificateFromMoodleInstancesAsync(MoodleInstanceUserIdsViewModel moodleUserInstanceUserIds, string filterText = ""); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs index 599569673..57fab7f83 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs @@ -105,11 +105,6 @@ public async Task GetRecentEnrolledCoursesAsy throw new ArgumentException("UserIds are required."); } - if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) - { - moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; - } - string statusFilter = string.Empty; ; if ((requestModel.Incomplete && requestModel.Complete) || (!requestModel.Incomplete && !requestModel.Complete)) @@ -193,11 +188,6 @@ public async Task GetEnrolledCoursesHistoryAs throw new ArgumentException("UserIds are required."); } - if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) - { - moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; - } - string statusFilter = string.Empty; ; if ((requestModel.Incomplete && requestModel.Complete) || (!requestModel.Incomplete && !requestModel.Complete)) @@ -269,17 +259,12 @@ public async Task GetInProgressEnrolledCourse { try { - email = "binon.yesudhas@nhs.net"; var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) { throw new ArgumentException("UserIds are required."); } - if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) - { - moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; - } string statusFilter = "inprogress"; @@ -335,17 +320,12 @@ public async Task GetUserCertificateAsync(string { try { - email = "binon.yesudhas@nhs.net"; var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) { throw new ArgumentException("UserIds are required."); } - if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) - { - moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; - } var client = await this.moodleBridgeHttpClient.GetClient(); // Build query string (optional params) var queryParams = new List(); @@ -407,10 +387,6 @@ public async Task GetUserCertificateFromMoodleIn { throw new ArgumentException("UserIds are required."); } - if (moodleUserInstanceUserIds.MoodleInstanceUserIds.ContainsKey("moodle-test")) - { - moodleUserInstanceUserIds.MoodleInstanceUserIds["moodle-test"] = 280; - } var client = await this.moodleBridgeHttpClient.GetClient(); var queryParams = new List(); @@ -492,7 +468,7 @@ public async Task> GetAllMoodleCategoriesAsync() catch (Exception ex) { this.logger.LogError(ex, "An error occurred while fetching user instances by email."); - throw; // Re-throw the exception to ensure the caller is aware of the failure + throw; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs index eb39f11cd..f7a0e11d2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using LearningHub.Nhs.OpenApi.Services.Interface.Services; + /// /// Moodle Bridge operations. /// From 7c8e67e579c2bafb88c24be41e6506880e56a1fc Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 26 Mar 2026 14:08:19 +0000 Subject: [PATCH 03/24] Added course url as part of moodle course object --- .../LearningHub.Nhs.AdminUI.csproj | 2 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 2 +- .../LearningHub.Nhs.WebUI.csproj | 2 +- .../Views/Catalogue/Courses.cshtml | 2 +- .../Views/Home/_LearningActivityCard.cshtml | 2 +- .../MyLearning/DownloadActivityRecords.cshtml | 2 +- .../Views/MyLearning/Index.cshtml | 16 +- .../Views/MyLearning/LearningHistory.cshtml | 196 +++++++++--------- .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 2 +- ...earningHub.Nhs.OpenApi.Repositories.csproj | 2 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../Services/DashboardService.cs | 1 + .../Services/MyLearningService.cs | 3 +- .../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +- .../LearningHub.NHS.OpenAPI.csproj | 2 +- ...ub.Nhs.ReportApi.Services.Interface.csproj | 2 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 2 +- .../LearningHub.Nhs.ReportApi.csproj | 2 +- .../LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 36 files changed, 141 insertions(+), 139 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index a8f070053..1894250bb 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index 23b92859c..e97e68a3d 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index f0521282a..e3155ea6f 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -112,7 +112,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml index 255049950..57dc0839c 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Courses.cshtml @@ -28,7 +28,7 @@ @foreach (Course item in Model.Courses) {
-

@item.Displayname

+

@item.Displayname

Type: Course
diff --git a/LearningHub.Nhs.WebUI/Views/Home/_LearningActivityCard.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_LearningActivityCard.cshtml index 799c4169d..af55e1c58 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_LearningActivityCard.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_LearningActivityCard.cshtml @@ -29,7 +29,7 @@ @if (ViewActivityHelper.GetResourceTypeDesc(Model.Item2.ResourceType) == "Course") { - @Model.Item2.Title + @Model.Item2.Title } else { diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml index c19e702e6..725c77b81 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/DownloadActivityRecords.cshtml @@ -1133,7 +1133,7 @@ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { - @activity.Title + @activity.Title } else { diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml index 5dc2d9990..b446f36d5 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/Index.cshtml @@ -121,7 +121,7 @@ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { - @activity.Title + @activity.Title } else { @@ -140,17 +140,17 @@ @displayText: - @if(isCompleted) + @if (isCompleted) { - - @certificateAwardedText - + + @certificateAwardedText + } else { - - @dateTimeText - + + @dateTimeText + }
diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml index d8410924f..d8bc1e4c7 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningHistory.cshtml @@ -57,7 +57,7 @@
@if (errorHasOccurred) { - + }

You can use this page to search and filter learning resources you've accessed and generate a report @@ -81,13 +81,13 @@

@if (Model.TotalCount != 0) { -
- -
+
+ +
} else { - + }
@@ -180,131 +180,131 @@ var dateTimeText = activityDate == today ? "Today" : activityDate == today.AddDays(-1) ? "Yesterday" : activityDate.ToString("dd MMM yyyy"); - var certicateAwardedDate = activity.CertificateAwardedDate.Date; - var certificateAwardedText = certicateAwardedDate == today ? "Today" - : certicateAwardedDate == today.AddDays(-1) ? "Yesterday" - : certicateAwardedDate.ToString("dd MMM yyyy"); + var certicateAwardedDate = activity.CertificateAwardedDate.Date; + var certificateAwardedText = certicateAwardedDate == today ? "Today" + : certicateAwardedDate == today.AddDays(-1) ? "Yesterday" + : certicateAwardedDate.ToString("dd MMM yyyy"); -
- - @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") +
+ + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { - @activity.Title + @activity.Title } else { - @activity.Title + @activity.Title } - -
-
-
- Type: + +
+
+
+ Type: - - @ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) - + + @ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) + - - @displayText: - - @if(isCompleted) - { - - @certificateAwardedText + + @displayText: - } - else - { - - @dateTimeText - - } -
-
- - - @statusText - - + @if (isCompleted) + { + + @certificateAwardedText + + } + else + { + + @dateTimeText + + } +
+
+ + + @statusText + + +
-
- @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") + @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { -
-
-
+
+
+
} else @if (ViewActivityHelper.CanViewVidoProgress(activity)) { -
- @activity.CompletionPercentage% complete - View Progress -
+
+ @activity.CompletionPercentage% complete + View Progress +
} @if (ViewActivityHelper.CanCertificateawarded(activity)) { -
- - - - - - - - - - - - - - - Certificate: - awarded [@certificateAwardedText] +
+ + + + + + + + + + + + + + + Certificate: + awarded [@certificateAwardedText] -
+
} else { -
- @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") +
+ @if (ViewActivityHelper.GetResourceTypeDesc(activity.ResourceType) == "Course") { -
@activity.CompletedActivities of @activity.TotalActivities activities completed
+
@activity.CompletedActivities of @activity.TotalActivities activities completed
} else { -
- No certificate available -
+
+ No certificate available +
} - @if (activity.CertificateEnabled == true) + @if (activity.CertificateEnabled == true) { -
- Includes a certificate - - - - - - - - - - - - - - - - - -
+
+ Includes a certificate + + + + + + + + + + + + + + + + + +
} -
+
}
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 a7ecc8897..90116952f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -17,7 +17,7 @@ - + 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 acfceeb83..26f2cda99 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 @@ -17,7 +17,7 @@ - + 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 33302f623..5bb10cb7d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -24,7 +24,7 @@ - + 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 802661079..30fb32a50 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 @@ -17,7 +17,7 @@ - + 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 793271d39..0faf9ca41 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -31,7 +31,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs index 5da2bc71b..bd98ac5b2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -291,6 +291,7 @@ public async Task GetMyInprogressLearning MinorVersion = 0, ResourceType = ResourceTypeEnum.Moodle, Title = course.DisplayName, + ResourceUrl = course.CourseUrl, CertificateEnabled = course.CertificateEnabled, ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, ActivityDate = course.LastAccessDate.HasValue diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index 12d9e42cd..7ff1bba1a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -231,6 +231,7 @@ public async Task GetUserRecentMyLearning MinorVersion = 0, ResourceType = ResourceTypeEnum.Moodle, Title = course.DisplayName, + ResourceUrl = course.CourseUrl, CertificateEnabled = course.CertificateEnabled, ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, ActivityDate = course.LastAccessDate.HasValue @@ -360,7 +361,6 @@ public async Task GetUserLearningHistoryA (activityStatusEnumFlag && !resourceTypeFlag && requestModel.Request.Courses) || (activityStatusEnumFlag && !resourceTypeFlag && !requestModel.Request.Courses)) { - ////entrolledCourses = await this.moodleApiService.GetEnrolledCoursesHistoryAsync(userId, requestModel.Request); entrolledCourses = await this.moodleBridgeApiService.GetEnrolledCoursesHistoryAsync(requestModel.MoodleInstanceUserIds, requestModel.Request); if (entrolledCourses != null) { @@ -378,6 +378,7 @@ public async Task GetUserLearningHistoryA MinorVersion = 0, ResourceType = ResourceTypeEnum.Moodle, Title = course.DisplayName, + ResourceUrl = course.CourseUrl, CertificateEnabled = course.CertificateEnabled, ActivityStatus = (course.Completed == true || course.ProgressPercentage.TrimEnd('%') == "100") ? ActivityStatusEnum.Completed : ActivityStatusEnum.Incomplete, ActivityDate = course.LastAccessDate.HasValue 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 27ab96576..d4f07f594 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 57eb26f4f..67b1a6344 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -19,7 +19,7 @@ - + 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 79f6af7f9..c4ab810b9 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 @@ -16,7 +16,7 @@ - + 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 c8ca7a2ae..eb8f7e4ac 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 @@ -18,7 +18,7 @@ - + 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 89bca5ef2..8d6b949b7 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + 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 ae1444ed9..de33549b3 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index c19afef98..291929c1d 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 0462105ff..b04f35acc 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + 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 8d695c492..4255bc9da 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + 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 100e6600d..9d001e011 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + 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 8273e219f..0e8453457 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index a2628d76c..386a36690 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + 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 73e99b72e..f54a7b091 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all 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 6b02d693a..8b77a79ff 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index d27db0e89..c01c4460f 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + 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 1a8367ea1..e5abf6ead 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 @@ -25,7 +25,7 @@ - + all 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 56c3518c1..39fab1878 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 @@ -9,7 +9,7 @@ - + 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 e4a707d63..b91543ad4 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 @@ -10,7 +10,7 @@ - + all 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 a6d13b605..f029a5642 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 @@ -9,7 +9,7 @@ - + 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 2e25b373f..a003bd00e 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 @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index e21f1b8e1..01c107bf2 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From b2a9c7725192738cf949e4047cefbd8345be45d8 Mon Sep 17 00:00:00 2001 From: Sarathlal Sarangadharan Date: Fri, 27 Mar 2026 10:09:20 +0000 Subject: [PATCH 04/24] TD-7051: LH prod db performance improvement and foreign key dependency removal from dependant tables --- .../Stored Procedures/Adf/AdfMergeHubUser.sql | 4 ---- .../Adf/proc_UpdateLastSyncTimeAdf.sql | Bin 572 -> 618 bytes 2 files changed, 4 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeHubUser.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeHubUser.sql index eeb1e5df8..396b57630 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeHubUser.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/AdfMergeHubUser.sql @@ -13,8 +13,6 @@ AS BEGIN SET NOCOUNT ON; - ALTER TABLE [hub].[user] NOCHECK CONSTRAINT FK_userTBL_userEmploymentTBL; - ALTER TABLE [elfh].[userEmploymentTBL] NOCHECK CONSTRAINT FK_userEmploymentTBL_userTBL; ALTER TABLE [hub].[User] NOCHECK CONSTRAINT ALL; MERGE [hub].[User] AS target @@ -65,8 +63,6 @@ BEGIN source.[regionId], source.[preferredTenantId],4, source.[CreateDate], source.[AmendUserId], source.[AmendDate], source.[Deleted] ); - ALTER TABLE [hub].[user] NOCHECK CONSTRAINT FK_userTBL_userEmploymentTBL; - ALTER TABLE [elfh].[userEmploymentTBL] NOCHECK CONSTRAINT FK_userEmploymentTBL_userTBL; ALTER TABLE [hub].[User] CHECK CONSTRAINT ALL; END GO diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/proc_UpdateLastSyncTimeAdf.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Adf/proc_UpdateLastSyncTimeAdf.sql index c7896b6b205a29ff5406ebc1e5a9b11609695d4e..ca01f742d8c7d6a054e218e243bcdb83deb11e11 100644 GIT binary patch delta 81 zcmdnP@``0c2cxS3gC|2UgC9dEgAWjDFgO6&sSM>nu}X$KhGd2ihD?TBpok8G0#H7Y Pp_rirLr!z@e8#x|xPlPp delta 35 qcmaFGvWI0u2cx(HgC9dGLpeh*LnT8VLo!1MLncEmL+a$SjB^34xCxU0 From b493b8ee68f820621bb06de7dcbf2b968142067c Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Tue, 7 Apr 2026 12:49:43 +0100 Subject: [PATCH 05/24] TD-7069: Call the MIB endpoint from wherever LH business logic exists for updating primary email address --- .../LearningHub.Nhs.AdminUI.csproj | 2 +- ...rningHub.Nhs.WebUI.AutomatedUiTests.csproj | 2 +- .../Controllers/MyAccountController.cs | 9 +++ .../Interfaces/IMoodleBridgeApiService.cs | 8 +++ .../LearningHub.Nhs.WebUI.csproj | 2 +- .../Services/MoodleBridgeApiService.cs | 42 ++++++++++++++ .../LearningHub.Nhs.OpenApi.Models.csproj | 2 +- ....Nhs.OpenApi.Repositories.Interface.csproj | 2 +- ...earningHub.Nhs.OpenApi.Repositories.csproj | 2 +- ...gHub.Nhs.OpenApi.Services.Interface.csproj | 2 +- .../Services/IMoodleBridgeApiService.cs | 8 +++ .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../Services/MoodleBridgeApiService.cs | 58 +++++++++++++++++-- .../LearningHub.Nhs.OpenApi.Tests.csproj | 2 +- .../Controllers/MoodleBridgeController.cs | 14 +++++ .../LearningHub.NHS.OpenAPI.csproj | 2 +- ...ub.Nhs.ReportApi.Services.Interface.csproj | 2 +- ...ub.Nhs.ReportApi.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Services.csproj | 2 +- .../LearningHub.Nhs.ReportApi.Shared.csproj | 2 +- .../LearningHub.Nhs.ReportApi.csproj | 2 +- .../LearningHub.Nhs.Api.csproj | 2 +- .../LearningHub.Nhs.Api.Shared.csproj | 2 +- .../LearningHub.Nhs.Api.UnitTests.csproj | 2 +- ...earningHub.Nhs.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.Repository.csproj | 2 +- .../LearningHub.Nhs.Services.Interface.csproj | 2 +- .../LearningHub.Nhs.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Services.csproj | 2 +- ...earningHub.Nhs.Migration.ConsoleApp.csproj | 2 +- ...LearningHub.Nhs.Migration.Interface.csproj | 2 +- .../LearningHub.Nhs.Migration.Models.csproj | 2 +- ...ub.Nhs.Migration.Staging.Repository.csproj | 2 +- ...LearningHub.Nhs.Migration.UnitTests.csproj | 2 +- .../LearningHub.Nhs.Migration.csproj | 2 +- 35 files changed, 163 insertions(+), 34 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index 1894250bb..1e0cbbb2d 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj index e97e68a3d..34a9455d2 100644 --- a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index bb24c03f8..13dcd7e2c 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -11,6 +11,7 @@ using GDS.MultiPageFormData; using GDS.MultiPageFormData.Enums; using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; @@ -97,6 +98,7 @@ public MyAccountController( this.multiPageFormService = multiPageFormService; this.cacheService = cacheService; this.configuration = configuration; + this.moodleBridgeApiService = moodleBridgeApiService; } private string LoginWizardCacheKey => $"{this.CurrentUserId}:LoginWizard"; @@ -1497,12 +1499,18 @@ public async Task ConfirmEmail(string token, string loctoken) var validationResult = await this.userService.ValidateEmailChangeTokenAsync(token, loctoken, isUserRoleUpgrade); EmailChangeValidateViewModel model = new EmailChangeValidateViewModel(); + UpdateEmailaddressViewModel emailModel = new UpdateEmailaddressViewModel() + { + OldEmail = user.EmailAddress, + NewEmail = validationResult.Email, + }; if (validationResult.Valid) { if (isUserRoleUpgrade) { await this.userService.UpgradeAsFullAccessUserAsync(validationResult.UserId, validationResult.Email); + await this.moodleBridgeApiService.UpdateEmail(emailModel); this.ViewBag.SuccessMessage = CommonValidationErrorMessages.EmailConfirmSucessMessage; model.Token = token; model.Loctoken = loctoken; @@ -1511,6 +1519,7 @@ public async Task ConfirmEmail(string token, string loctoken) else { await this.userService.UpdateUserPrimaryEmailAsync(validationResult.Email); + await this.moodleBridgeApiService.UpdateEmail(emailModel); // Add UserHistory entry UserHistoryViewModel userHistory = new UserHistoryViewModel() diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs index e316bbf84..87cb49cf6 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Models; using MoodleCourseCompletionModel = LearningHub.Nhs.Models.Moodle.API.MoodleCourseCompletionModel; @@ -18,5 +19,12 @@ public interface IMoodleBridgeApiService /// The email. /// A representing the result of the asynchronous operation. Task GetUserInstancesByEmail(string email); + + /// + /// UpdateEmail. + /// + /// The updateEmailaddressViewModel. + /// A representing the result of the asynchronous operation. + Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel); } } diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index e3155ea6f..9e1f484cb 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -112,7 +112,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs index f00592e01..6cd57f10c 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs @@ -2,10 +2,12 @@ { using System; using System.Collections.Generic; + using System.Net.Http.Json; using System.Text.Json; using System.Threading.Tasks; using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.Models.User; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; @@ -63,5 +65,45 @@ public async Task GetUserInstancesByEmail(string return viewmodel; } } + + /// + /// UpdateEmail. + /// + /// The updateEmailaddressViewModel. + /// email update status. + public async Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel) + { + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var requestUrl = "MoodleBridge/UpdateEmail"; + + var response = await client.PostAsJsonAsync(requestUrl, updateEmailaddressViewModel) + .ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var viewModel = await response.Content + .ReadFromJsonAsync(); + + return viewModel; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"API Error: {response.StatusCode}, Details: {errorContent}"); + } + } + catch (Exception ex) + { + throw new Exception("Failed to update user email on moodle instances"); + } + } } } 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 90116952f..f93a18aa5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -17,7 +17,7 @@ - + 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 26f2cda99..043795e81 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 @@ -17,7 +17,7 @@ - + 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 5bb10cb7d..29d8422f7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/LearningHub.Nhs.OpenApi.Repositories.csproj @@ -24,7 +24,7 @@ - + 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 30fb32a50..4f1227545 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 @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs index a3aa57b0d..3bd55103b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs @@ -4,6 +4,7 @@ using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Models.MyLearning; +using LearningHub.Nhs.Models.User; namespace LearningHub.Nhs.OpenApi.Services.Interface.Services { @@ -19,6 +20,13 @@ public interface IMoodleBridgeApiService /// A representing the result of the asynchronous operation. Task GetUserInstancesByEmail(string email); + /// + /// UpdateEmail. + /// + /// TheupdateEmailaddressViewModel. + /// + Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel); + /// /// GetRecentEnrolledCoursesAsync. /// 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 0faf9ca41..291dbe784 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -31,7 +31,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs index 57fab7f83..80dd76170 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs @@ -21,6 +21,7 @@ using static System.Net.WebRequestMethods; using IdentityModel.Client; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + using LearningHub.Nhs.Models.User; /// /// MoodleBridgeApiService. @@ -84,7 +85,54 @@ public async Task GetUserInstancesByEmail(string catch (Exception ex) { this.logger.LogError(ex, "An error occurred while fetching user instances by email."); - throw; + throw; + } + } + + /// + /// UpdateEmail. + /// + /// The UpdateEmailaddressViewModel. + /// + public async Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel) + { + try + { + var client = await this.moodleBridgeHttpClient.GetClient(); + + var requestUrl = "/api/v1/Users/update-email"; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + var response = await client.PostAsJsonAsync(requestUrl, updateEmailaddressViewModel) + .ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + + var viewModel = System.Text.Json.JsonSerializer.Deserialize(result, options); + + return viewModel; + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"API Error: {response.StatusCode}, Details: {errorContent}"); + } + } + catch (Exception ex) + { + this.logger.LogError(ex, "An error occurred while updating email."); + throw; } } @@ -168,7 +216,7 @@ public async Task GetRecentEnrolledCoursesAsy catch (Exception ex) { this.logger.LogError(ex, "An error occurred while fetching user's recent learning activities "); - throw; + throw; } } @@ -333,7 +381,7 @@ public async Task GetUserCertificateAsync(string { queryParams.Add($"searchterm={Uri.EscapeDataString(filterText)}"); } - + var queryString = queryParams.Any() ? "?" + string.Join("&", queryParams) : string.Empty; @@ -468,7 +516,7 @@ public async Task> GetAllMoodleCategoriesAsync() catch (Exception ex) { this.logger.LogError(ex, "An error occurred while fetching user instances by email."); - throw; + throw; } } @@ -553,7 +601,7 @@ public async Task> GetSubCategoryByCategoryIdAsync(strin catch (Exception ex) { this.logger.LogError(ex, "An error occurred while fetching sub categories by category id."); - throw; + throw; } } } 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 d4f07f594..f4251a236 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs index f7a0e11d2..5f8b67774 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using LearningHub.Nhs.Models.User; /// /// Moodle Bridge operations. @@ -37,6 +38,19 @@ public async Task GetUserInstancesByEmail(string email) return this.Ok(moodleUser); } + /// + /// The GetMoodle Instances UserIds. + /// + /// The LH user email. + /// The . + [HttpPost] + [Route("UpdateEmail")] + public async Task UpdateEmail([FromBody] UpdateEmailaddressViewModel updateEmailaddressViewModel) + { + var emailUpdateResponse = await this.moodleBridgeApiService.UpdateEmail(updateEmailaddressViewModel); + return this.Ok(emailUpdateResponse); + } + /// /// GetAllMoodleCategoriesAsync. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 67b1a6344..b4bfab382 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -19,7 +19,7 @@ - + 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 c4ab810b9..96ff6ae13 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 @@ -16,7 +16,7 @@ - + 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 eb8f7e4ac..70e3e863b 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 @@ -18,7 +18,7 @@ - + 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 8d6b949b7..c4944deb0 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + 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 de33549b3..d02c44881 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index 291929c1d..5ad3e0d51 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index b04f35acc..ce240b56f 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + 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 4255bc9da..12b019206 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + 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 9d001e011..70c6f8f9d 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + 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 0e8453457..0ff751617 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 386a36690..b206c9818 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + 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 f54a7b091..31ce5eb56 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all 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 8b77a79ff..a91b6e0b0 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index c01c4460f..c44ed1fde 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + 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 e5abf6ead..a40466948 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 @@ -25,7 +25,7 @@ - + all 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 39fab1878..c2ac38a24 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 @@ -9,7 +9,7 @@ - + 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 b91543ad4..a10f69fd6 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 @@ -10,7 +10,7 @@ - + all 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 f029a5642..ba2b9fc16 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 @@ -9,7 +9,7 @@ - + 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 a003bd00e..d555f0d12 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 @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index 01c107bf2..c00e268d3 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From d9120d9b0f6b6dabc533233e54b8c65139c08b95 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 9 Apr 2026 09:49:53 +0100 Subject: [PATCH 06/24] TD-7108: My courses and elearning Tray - eLearning and the Recently Completed section should display a message when there is no content instead of displaying blank --- .../Views/Home/_MyCoursesAndElearning.cshtml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml index 8923e8e24..394398de7 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml @@ -93,7 +93,7 @@ } - @if (isLastPage && pagingViewModel.TotalCount >=8) + @if (isLastPage && pagingViewModel.TotalCount >= 8) {
  • @@ -190,16 +190,17 @@ else {
    - @if (Model.MyLearnings.Type == "my-in-progress" && Model.MyLearnings.TotalCount <= 0) - { -

    You have not accessed any learning yet.

    -

    Accessed resources and courses will be displayed here. Browse from the list of resources below or search for a specific term using the search bar at the top of the screen.

    - } - else if (Model.MyLearnings.Type == "my-certificates" && Model.MyLearnings.TotalCount <= 0) + @if (Model.MyLearnings.Type == "my-certificates" && Model.MyLearnings.TotalCount <= 0) {

    You have no certificates.

    Certificates when available will be displayed here.

    } + else if (Model.DashboardTrayLearningResourceType == "elearning" && Model.MyLearnings.TotalCount <= 0) + { +

    You have not accessed any learning yet.

    +

    Accessed resources and courses will be displayed here. Browse from the list of resources below or search for a specific term using the search bar at the top of the screen.

    + } +
    }
    From bccb37cb77a4fe6acd4a269d6ee904f9b7325c2d Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 9 Apr 2026 10:56:21 +0100 Subject: [PATCH 07/24] Correc ted for recently completed tray --- .../Views/Home/_ResourceTray.cshtml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml index b17ea20fb..0d812f20c 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml @@ -69,9 +69,9 @@
    -
      - @if (@Model.Resources.Resources != null) - { + @if (@Model.Resources.Resources.Count() > 0) + { +
        @foreach (var resource in Model.Resources.Resources) { var resourceViewModel = new Tuple("resources", resource); @@ -79,8 +79,16 @@ } +
      + } + else + { + if (Model.Resources.Type == "my-recent-completed") + { +

      You have not recently completed any learning.

      +

      When available your completed learning will be displayed here.

      } -
    + }
    From a28dface913ee29a6e07778fd38923f9316439db Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 9 Apr 2026 13:13:55 +0100 Subject: [PATCH 08/24] handled empty records --- .../Views/Home/_MyCoursesAndElearning.cshtml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml index 394398de7..dc5880065 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml @@ -200,6 +200,16 @@

    You have not accessed any learning yet.

    Accessed resources and courses will be displayed here. Browse from the list of resources below or search for a specific term using the search bar at the top of the screen.

    } + else if (Model.DashboardTrayLearningResourceType == "courses" && Model.MyLearnings.TotalCount <= 0) + { +

    You have not accessed any coursees yet.

    +

    Accessed resources and courses will be displayed here. Browse from the list of resources below or search for a specific term using the search bar at the top of the screen.

    + } + else if (Model.DashboardTrayLearningResourceType == "all" && Model.MyLearnings.TotalCount <= 0) + { +

    You have not accessed any learning yet.

    +

    Accessed resources and courses will be displayed here. Browse from the list of resources below or search for a specific term using the search bar at the top of the screen.

    + }
    } From c43b6a6248e190deb3ec1c4c5f1dcfe01ae17636 Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 10 Apr 2026 09:25:39 +0100 Subject: [PATCH 09/24] TD-7116, script to create new moodle instance config table and and script to insert default moodle prod instacne --- .../LearningHub.Nhs.Database.sqlproj | 3 ++ .../Scripts/TD-7116-mib_new_env.sql | 4 +++ .../Tables/MIB/MoodleInstanceConfigs.sql | 28 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/MIB/MoodleInstanceConfigs.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index a6a0db7ad..705fc366f 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -102,6 +102,7 @@ + @@ -565,6 +566,8 @@ + + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql new file mode 100644 index 000000000..f8ab9e17e --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql @@ -0,0 +1,4 @@ + INSERT INTO MoodleInstanceConfigs + (ShortName, BaseUrl, TokenSecretName, EnabledEndpoints, Weighting, IsEnabled, CreatedAt, UpdatedAt) +VALUES + ('moodle-prod', 'https://learn.learninghub.nhs.uk/', 'LearningHubMoodleClientSecretProd', 'users,courses,grades', 100, 1, GETUTCDATE(), GETUTCDATE()); \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/MIB/MoodleInstanceConfigs.sql b/WebAPI/LearningHub.Nhs.Database/Tables/MIB/MoodleInstanceConfigs.sql new file mode 100644 index 000000000..956319c6b --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/MIB/MoodleInstanceConfigs.sql @@ -0,0 +1,28 @@ +CREATE TABLE [dbo].[MoodleInstanceConfigs]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [BaseUrl] [nvarchar](500) NOT NULL, + [ShortName] [nvarchar](100) NOT NULL, + [TokenSecretName] [nvarchar](200) NOT NULL, + [EnabledEndpoints] [nvarchar](1000) NULL, + [IsEnabled] [bit] NOT NULL, + [Weighting] [int] NOT NULL, + [CreatedAt] [datetime2](7) NOT NULL, + [UpdatedAt] [datetime2](7) NOT NULL, + CONSTRAINT [PK_InstanceConfigs] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO + +ALTER TABLE [dbo].[MoodleInstanceConfigs] ADD CONSTRAINT [DF_InstanceConfigs_IsEnabled] DEFAULT ((1)) FOR [IsEnabled] +GO + +ALTER TABLE [dbo].[MoodleInstanceConfigs] ADD CONSTRAINT [DF_InstanceConfigs_Weighting] DEFAULT ((100)) FOR [Weighting] +GO + +ALTER TABLE [dbo].[MoodleInstanceConfigs] ADD CONSTRAINT [DF_InstanceConfigs_CreatedAt] DEFAULT (sysutcdatetime()) FOR [CreatedAt] +GO + +ALTER TABLE [dbo].[MoodleInstanceConfigs] ADD CONSTRAINT [DF_InstanceConfigs_UpdatedAt] DEFAULT (sysutcdatetime()) FOR [UpdatedAt] +GO \ No newline at end of file From 9ce2337ea0a3082dc6babd360888d138020fa1be Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 10 Apr 2026 09:42:14 +0100 Subject: [PATCH 10/24] Made it consistent with other script inserts --- .../Scripts/Post-Deploy/Script.PostDeployment.sql | 1 + .../Post-Deploy/Scripts/TD-7116-mib_new_env.sql | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index 34ba8ad39..672b5785b 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -86,4 +86,5 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\AttributeData.sql :r .\Scripts\PPSXFileType.sql :r .\Scripts\UpdateFileTypes.sql +:r .\Scripts\TD-7116-mib_new_env.sql diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql index f8ab9e17e..09e405b1d 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql @@ -1,4 +1,8 @@ - INSERT INTO MoodleInstanceConfigs - (ShortName, BaseUrl, TokenSecretName, EnabledEndpoints, Weighting, IsEnabled, CreatedAt, UpdatedAt) -VALUES - ('moodle-prod', 'https://learn.learninghub.nhs.uk/', 'LearningHubMoodleClientSecretProd', 'users,courses,grades', 100, 1, GETUTCDATE(), GETUTCDATE()); \ No newline at end of file + +IF NOT EXISTS(SELECT 'X' FROM [dbo].[MoodleInstanceConfigs] WHERE ShortName = 'moodle-prod') +BEGIN + INSERT INTO [dbo].[MoodleInstanceConfigs] + (ShortName, BaseUrl, TokenSecretName, EnabledEndpoints, Weighting, IsEnabled, CreatedAt, UpdatedAt) + VALUES + ('moodle-prod', 'https://learn.learninghub.nhs.uk/', 'LearningHubMoodleClientSecretProd', 'users,courses,grades', 100, 1, GETUTCDATE(), GETUTCDATE()); +END \ No newline at end of file From d688d7dfd17b935bd2326ad93f84dd7296b7b2d0 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Mon, 13 Apr 2026 15:57:01 +0100 Subject: [PATCH 11/24] Resume Change Tracking after each deployment. --- .../LearningHub.Nhs.Database.sqlproj | 1 + .../Post-Deploy/Script.PostDeployment.sql | 1 + .../TD-7106-Resume-Databricks-Ingestion.sql | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 705fc366f..f9b533c16 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -568,6 +568,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index 672b5785b..acee39c19 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -87,4 +87,5 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\PPSXFileType.sql :r .\Scripts\UpdateFileTypes.sql :r .\Scripts\TD-7116-mib_new_env.sql +:r .\Script\TD-7106-Resume-Databricks-Ingestion.sql diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql new file mode 100644 index 000000000..710a7f897 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql @@ -0,0 +1,17 @@ +IF EXISTS ( + SELECT 1 + FROM sys.change_tracking_databases + WHERE database_id = DB_ID() +) +BEGIN + PRINT 'Change Tracking is enabled. Executing setup...'; + + EXEC dbo.lakeflowSetupChangeTracking + @Tables = 'activity.ResourceActivity', --(to include all other tables with CT enabled) + @User = 'Elfhadmin', + @Retention = '2 DAYS'; +END +ELSE +BEGIN + PRINT 'Change Tracking is NOT enabled on this database. Skipping execution.'; +END \ No newline at end of file From 8c6ff38b16131d8df8ed50e2b17f64f77d136f36 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Mon, 13 Apr 2026 17:18:28 +0100 Subject: [PATCH 12/24] . --- .../Scripts/Post-Deploy/Script.PostDeployment.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index acee39c19..2b2bcda6e 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -87,5 +87,4 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\PPSXFileType.sql :r .\Scripts\UpdateFileTypes.sql :r .\Scripts\TD-7116-mib_new_env.sql -:r .\Script\TD-7106-Resume-Databricks-Ingestion.sql - +:r .\Scripts\TD-7106-Resume-Databricks-Ingestion.sql From c0c372ab57d3391f76838a7063f93793a3fafd39 Mon Sep 17 00:00:00 2001 From: Binon Date: Tue, 14 Apr 2026 11:24:19 +0100 Subject: [PATCH 13/24] removed the findwise references in WebAPI --- .../Controllers/HierarchyController.cs | 6 - .../Controllers/ResourceSyncController.cs | 4 +- .../Startup/ServiceMappings.cs | 3 - .../Configuration/MigrationToolSettings.cs | 2 +- .../IFindWiseHttpClient.cs | 18 - .../IFindwiseApiFacade.cs | 37 -- .../IResourceService.cs | 2 +- .../CatalogueServiceTests.cs | 3 - .../LearningHub.Nhs.Services/BaseService.cs | 18 +- .../CatalogueService.cs | 76 +--- .../FindWiseHttpClient.cs | 60 --- .../Findwise/FindwiseApiFacade.cs | 131 ------- .../HierarchyService.cs | 2 +- .../Messaging/MessageService.cs | 5 +- .../NotificationTemplateService.cs | 4 +- .../LearningHub.Nhs.Services/RatingService.cs | 2 - .../ResourceService.cs | 4 +- .../ResourceSyncService.cs | 61 +-- .../LearningHub.Nhs.Services/SearchService.cs | 352 +----------------- 19 files changed, 23 insertions(+), 767 deletions(-) delete mode 100644 WebAPI/LearningHub.Nhs.Services.Interface/IFindWiseHttpClient.cs delete mode 100644 WebAPI/LearningHub.Nhs.Services.Interface/IFindwiseApiFacade.cs delete mode 100644 WebAPI/LearningHub.Nhs.Services/FindWiseHttpClient.cs delete mode 100644 WebAPI/LearningHub.Nhs.Services/Findwise/FindwiseApiFacade.cs diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/HierarchyController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/HierarchyController.cs index ff6d5d560..3af6c645e 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/HierarchyController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/HierarchyController.cs @@ -473,12 +473,6 @@ public async Task PublishHierarchyEdit(PublishHierarchyEditViewMo try { int publicationId = await this.hierarchyService.PublishHierarchyEditAsync(publishViewModel); - - /* TODO - IT2 - confirm submission to Findwise (why here rather than in service method just called?) - if (publicationId > 0) - { - this.hierarchyService.ConfirmSubmissionToSearch(publicationId, publishViewModel.UserId); - } */ } catch (Exception ex) { diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/ResourceSyncController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/ResourceSyncController.cs index 0228c54b2..8846029e1 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/ResourceSyncController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/ResourceSyncController.cs @@ -67,11 +67,11 @@ public async Task RemoveFromSyncList(List resourceIds) } /// - /// The SyncWithFindwise. + /// The Sync. /// /// The action result. [HttpPost("Sync")] - public async Task SyncWithFindwise() + public async Task Sync() { var vr = await this.resourceSyncService.SyncForUserAsync(this.CurrentUserId); return this.Ok(new ApiResponse(vr.IsValid, vr)); diff --git a/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs b/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs index a9f0988ce..108504bc2 100644 --- a/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs +++ b/WebAPI/LearningHub.Nhs.API/Startup/ServiceMappings.cs @@ -39,7 +39,6 @@ namespace LearningHub.Nhs.Api using LearningHub.Nhs.Repository.Report; using LearningHub.Nhs.Repository.Resources; using LearningHub.Nhs.Services; - using LearningHub.Nhs.Services.Findwise; using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.Services.Interface.Messaging; using LearningHub.Nhs.Services.Interface.Report; @@ -218,7 +217,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -235,7 +233,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddTransient(); services.AddTransient(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/MigrationToolSettings.cs b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/MigrationToolSettings.cs index bcfa50e66..76e66990c 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/MigrationToolSettings.cs +++ b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/MigrationToolSettings.cs @@ -1,7 +1,7 @@ namespace LearningHub.Nhs.Api.Shared.Configuration { /// - /// The FindwiseSettings. + /// The MigrationToolSettings. /// public class MigrationToolSettings { diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/IFindWiseHttpClient.cs b/WebAPI/LearningHub.Nhs.Services.Interface/IFindWiseHttpClient.cs deleted file mode 100644 index 74cc6f31f..000000000 --- a/WebAPI/LearningHub.Nhs.Services.Interface/IFindWiseHttpClient.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace LearningHub.Nhs.Services.Interface -{ - using System.Net.Http; - using System.Threading.Tasks; - - /// - /// The FindWise Http Client interface. - /// - public interface IFindWiseHttpClient - { - /// - /// The get cient async. - /// - /// The url of the client. - /// The . - Task GetClient(string httpClientUrl); - } -} \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/IFindwiseApiFacade.cs b/WebAPI/LearningHub.Nhs.Services.Interface/IFindwiseApiFacade.cs deleted file mode 100644 index 4e50ff92b..000000000 --- a/WebAPI/LearningHub.Nhs.Services.Interface/IFindwiseApiFacade.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace LearningHub.Nhs.Services.Interface -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using LearningHub.Nhs.Models.Search; - - /// - /// The IFindwiseApiFacade. - /// - public interface IFindwiseApiFacade - { - /// - /// Modifies the information Findwise has for the catalogues provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The catalogues to add/replace in the index. - /// The task. - Task AddOrReplaceAsync(List catalogues); - - /// - /// Modifies the information Findwise has for the resources provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The resources to add/replace in the index. - /// The task. - Task AddOrReplaceAsync(List resources); - - /// - /// Removes the documents from Findwise. - /// - /// The resources to remove from Findwise. - /// The task. - Task RemoveAsync(List resources); - } -} diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/IResourceService.cs b/WebAPI/LearningHub.Nhs.Services.Interface/IResourceService.cs index 9b2e8e9d4..b31374b16 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/IResourceService.cs +++ b/WebAPI/LearningHub.Nhs.Services.Interface/IResourceService.cs @@ -165,7 +165,7 @@ public interface IResourceService Task PublishResourceVersionAsync(PublishViewModel publishViewModel); /// - /// Submits a published resource version to the Findwise search. + /// Submits a published resource version to search. /// /// The resourceVersionId. /// The userId. diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/CatalogueServiceTests.cs b/WebAPI/LearningHub.Nhs.Services.UnitTests/CatalogueServiceTests.cs index 4157f98cb..f7f49ec59 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/CatalogueServiceTests.cs +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/CatalogueServiceTests.cs @@ -28,7 +28,6 @@ public class CatalogueServiceTests : TestBase { private readonly Mock mockUserRepository; private readonly Mock mockResourceVersionRepository; - private readonly Mock mockFindwiseApiFacade; private Mock mockRoleUserGroupRepository; private Mock mockUserUserGroupRepository; private MockRepository mockRepository; @@ -62,7 +61,6 @@ public CatalogueServiceTests() this.mockRoleUserGroupRepository = this.mockRepository.Create(); this.mockUserRepository = this.mockRepository.Create(); this.mockResourceVersionRepository = this.mockRepository.Create(); - this.mockFindwiseApiFacade = this.mockRepository.Create(); this.mockRoleUserGroupRepository = this.mockRepository.Create(); this.mockUserUserGroupRepository = this.mockRepository.Create(); this.mockEmailSenderService = this.mockRepository.Create(); @@ -142,7 +140,6 @@ private CatalogueService CreateService() this.mockUserRepository.Object, this.mockRoleUserGroupRepository.Object, this.mockResourceVersionRepository.Object, - this.mockFindwiseApiFacade.Object, this.mockUserUserGroupRepository.Object, this.mockEmailSenderService.Object, ////this.mockUserService.Object, diff --git a/WebAPI/LearningHub.Nhs.Services/BaseService.cs b/WebAPI/LearningHub.Nhs.Services/BaseService.cs index 2d9ae36dc..6dc0b1779 100644 --- a/WebAPI/LearningHub.Nhs.Services/BaseService.cs +++ b/WebAPI/LearningHub.Nhs.Services/BaseService.cs @@ -1,6 +1,5 @@ namespace LearningHub.Nhs.Services { - using LearningHub.Nhs.Services.Interface; using Microsoft.Extensions.Logging; /// @@ -16,20 +15,13 @@ public abstract class BaseService /// private readonly ILogger logger; - /// - /// The Find Wise HTTP Client. - /// - private IFindWiseHttpClient findWiseHttpClient; - /// /// Initializes a new instance of the class. /// The base service. /// - /// The Find Wise http client. /// The logger. - protected BaseService(IFindWiseHttpClient findWiseHttpClient, ILogger logger) + protected BaseService(ILogger logger) { - this.findWiseHttpClient = findWiseHttpClient; this.logger = logger; } @@ -40,13 +32,5 @@ protected ILogger Logger { get { return this.logger; } } - - /// - /// Gets the Find Wise HTTP Client. - /// - protected IFindWiseHttpClient FindWiseHttpClient - { - get { return this.findWiseHttpClient; } - } } } diff --git a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs index c9906b62f..370c876c7 100644 --- a/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs +++ b/WebAPI/LearningHub.Nhs.Services/CatalogueService.cs @@ -40,7 +40,6 @@ public class CatalogueService : ICatalogueService private readonly IUserRepository userRepository; private readonly IRoleUserGroupRepository roleUserGroupRepository; private readonly IResourceVersionRepository resourceVersionRepository; - private readonly IFindwiseApiFacade findwiseApiFacade; private readonly IUserUserGroupRepository userUserGroupRepository; private readonly IEmailSenderService emailSenderService; private readonly Settings settings; @@ -90,7 +89,6 @@ public CatalogueService( IUserRepository userRepository, IRoleUserGroupRepository roleUserGroupRepository, IResourceVersionRepository resourceVersionRepository, - IFindwiseApiFacade findwiseApiFacade, IUserUserGroupRepository userUserGroupRepository, IEmailSenderService emailSenderService, ////elfhHub.Nhs.Services.Interface.IUserService userService, @@ -108,7 +106,6 @@ public CatalogueService( this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; this.nodeResourceRepository = nodeResourceRepository; this.resourceVersionRepository = resourceVersionRepository; - this.findwiseApiFacade = findwiseApiFacade; this.roleUserGroupRepository = roleUserGroupRepository; this.userUserGroupRepository = userUserGroupRepository; this.nodeActivityRepository = nodeActivityRepository; @@ -250,24 +247,6 @@ public async Task CreateCatalogueAsync(int userId, var catalogueNodeVersionId = await this.catalogueNodeVersionRepository.CreateCatalogueAsync(userId, catalogue); - // Catalogue is in database, push to findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.Id == catalogueNodeVersionId); - - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.settings.Findwise.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.settings.Findwise.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true) { CreatedId = catalogueNodeVersionId, @@ -486,24 +465,6 @@ public async Task UpdateCatalogueAsync(int userId, await this.catalogueNodeVersionRepository.UpdateCatalogueAsync(userId, catalogue); - // Update catalogue in findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.Id == catalogue.CatalogueNodeVersionId); - - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.settings.Findwise.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.settings.Findwise.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true); } @@ -579,24 +540,6 @@ public async Task ShowCatalogueAsync(int userId, in await this.catalogueNodeVersionRepository.ShowCatalogue(userId, nodeId); - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.NodeVersion.NodeId == nodeId); - - // update findwise - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.settings.Findwise.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.settings.Findwise.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true); } @@ -615,24 +558,7 @@ public async Task HideCatalogueAsync(int userId, int nodeId) } node.Hidden = true; - await this.nodeRepository.UpdateAsync(userId, node); - - // update findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.NodeVersion.NodeId == nodeId); - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.settings.Findwise.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.settings.Findwise.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } + await this.nodeRepository.UpdateAsync(userId, node); } /// diff --git a/WebAPI/LearningHub.Nhs.Services/FindWiseHttpClient.cs b/WebAPI/LearningHub.Nhs.Services/FindWiseHttpClient.cs deleted file mode 100644 index 51a2fa0cb..000000000 --- a/WebAPI/LearningHub.Nhs.Services/FindWiseHttpClient.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace LearningHub.Nhs.Services -{ - using System; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading.Tasks; - using LearningHub.Nhs.Services.Interface; - - /// - /// The FindWise Http Client. - /// - public class FindWiseHttpClient : IFindWiseHttpClient, IDisposable - { - private readonly HttpClient httpClient = new (); - //// private readonly LearningHubAuthServiceConfig authConfig; - private bool initialised = false; - - /// - /// The Get Client method. - /// - /// The url of the client. - /// The . - public async Task GetClient(string httpClientUrl) - { - this.Initialise(httpClientUrl); - return this.httpClient; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// The dispoase. - /// - /// disposing. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.httpClient.Dispose(); - } - } - - private void Initialise(string httpClientUrl) - { - if (this.initialised == false) - { - this.httpClient.BaseAddress = new Uri(httpClientUrl); - this.httpClient.DefaultRequestHeaders.Accept.Clear(); - this.httpClient.DefaultRequestHeaders.Accept.Add( - new MediaTypeWithQualityHeaderValue("application/json")); - this.initialised = true; - } - } - } -} diff --git a/WebAPI/LearningHub.Nhs.Services/Findwise/FindwiseApiFacade.cs b/WebAPI/LearningHub.Nhs.Services/Findwise/FindwiseApiFacade.cs deleted file mode 100644 index 06911ba10..000000000 --- a/WebAPI/LearningHub.Nhs.Services/Findwise/FindwiseApiFacade.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace LearningHub.Nhs.Services.Findwise -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading.Tasks; - using LearningHub.Nhs.Api.Shared.Configuration; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Services.Interface; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; - - /// - /// The FindwiseApiFacade. - /// - public class FindwiseApiFacade : IFindwiseApiFacade - { - private readonly IFindWiseHttpClient findWiseHttpClient; - private readonly Settings settings; - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// The findWiseHttpClient. - /// The options. - /// The logger. - public FindwiseApiFacade( - IFindWiseHttpClient findWiseHttpClient, - IOptions options, - ILogger logger) - { - this.findWiseHttpClient = findWiseHttpClient; - this.settings = options.Value; - this.logger = logger; - } - - /// - /// Modifies the information Findwise has for the catalogues provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The catalogues to add/replace in the index. - /// The task. - public async Task AddOrReplaceAsync(List catalogues) - { - var request = string.Format(this.settings.Findwise.IndexMethod, this.settings.Findwise.CollectionIds.Catalogue) - + $"?token={this.settings.Findwise.Token}"; - var response = await this.PostAsync(request, catalogues); - this.ValidateResponse(response, $"catalogues: {string.Join(',', catalogues.Select(x => x.Id))}"); - } - - /// - /// Modifies the information Findwise has for the resources provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The resources to add/replace in the index. - /// The task. - public async Task AddOrReplaceAsync(List resources) - { - var request = string.Format(this.settings.Findwise.IndexMethod, this.settings.Findwise.CollectionIds.Resource) - + $"?token={this.settings.Findwise.Token}"; - var response = await this.PostAsync(request, resources); - this.ValidateResponse(response, $"resources: {string.Join(',', resources.Select(x => x.Id))}"); - } - - /// - /// Removes the documents from Findwise. - /// - /// The resources to remove from Findwise. - /// The task. - public async Task RemoveAsync(List resources) - { - var resourceIds = resources.Select(x => x.Id); - var idQueryString = string.Join( - '&', - resourceIds.Select(x => $"id={x}")); - var request = string.Format( - this.settings.Findwise.IndexMethod, - this.settings.Findwise.CollectionIds.Resource) - + $"?{idQueryString}&token={this.settings.Findwise.Token}"; - var response = await this.DeleteAsync(request); - this.ValidateResponse(response, $"resources: {string.Join(',', resourceIds)}"); - } - - private void ValidateResponse(HttpResponseMessage response, string dataForError) - { - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Updating FindWise failed for {dataForError} HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - this.logger.LogError($"Updating FindWise failed for {dataForError} HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("Posting of resource to search failed: " + dataForError); - } - } - - private async Task PostAsync(string request, T obj) - where T : class, new() - { - var content = this.GetContent(obj); - var client = await this.GetClientAsync(); - return await client.PostAsync(request, content).ConfigureAwait(false); - } - - private async Task DeleteAsync(string request) - { - var client = await this.GetClientAsync(); - return await client.DeleteAsync(request).ConfigureAwait(false); - } - - private async Task GetClientAsync() - { - return await this.findWiseHttpClient.GetClient(this.settings.Findwise.IndexUrl); - } - - private StringContent GetContent(T obj) - where T : class, new() - { - var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); - - return new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - } - } -} diff --git a/WebAPI/LearningHub.Nhs.Services/HierarchyService.cs b/WebAPI/LearningHub.Nhs.Services/HierarchyService.cs index 5a2d6734d..4df4915a2 100644 --- a/WebAPI/LearningHub.Nhs.Services/HierarchyService.cs +++ b/WebAPI/LearningHub.Nhs.Services/HierarchyService.cs @@ -651,7 +651,7 @@ public async Task PublishHierarchyEditAsync(PublishHierarchyEditViewModel p var currentUserId = publishViewModel.UserId; var publicationId = this.hierarchyEditRepository.Publish(publishViewModel.HierarchyEditId, publishViewModel.IsMajorRevision, publishViewModel.Notes, currentUserId); - bool success = true; // From Findwise in IT2 + bool success = true; if (success) { diff --git a/WebAPI/LearningHub.Nhs.Services/Messaging/MessageService.cs b/WebAPI/LearningHub.Nhs.Services/Messaging/MessageService.cs index fa28f33b1..98a8d2610 100644 --- a/WebAPI/LearningHub.Nhs.Services/Messaging/MessageService.cs +++ b/WebAPI/LearningHub.Nhs.Services/Messaging/MessageService.cs @@ -9,7 +9,6 @@ using LearningHub.Nhs.Models.Messaging; using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.Repository.Interface.Messaging; - using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.Services.Interface.Messaging; using Microsoft.Extensions.Logging; @@ -23,14 +22,12 @@ public class MessageService : BaseService, IMessageService /// /// Initializes a new instance of the class. /// - /// The findwiseHttpClient. /// The logger. /// The message repository. public MessageService( - IFindWiseHttpClient findwiseHttpClient, ILogger logger, IMessageRepository messageRepository) - : base(findwiseHttpClient, logger) + : base(logger) { this.messageRepository = messageRepository; } diff --git a/WebAPI/LearningHub.Nhs.Services/NotificationTemplateService.cs b/WebAPI/LearningHub.Nhs.Services/NotificationTemplateService.cs index 8452f03b5..d1934b338 100644 --- a/WebAPI/LearningHub.Nhs.Services/NotificationTemplateService.cs +++ b/WebAPI/LearningHub.Nhs.Services/NotificationTemplateService.cs @@ -20,14 +20,12 @@ public class NotificationTemplateService : BaseService, IN /// /// Initializes a new instance of the class. /// - /// The findwise client. /// The logger. /// The notification template repository. public NotificationTemplateService( - IFindWiseHttpClient fwClient, ILogger logger, INotificationTemplateRepository notificationTemplateRepository) - : base(fwClient, logger) + : base(logger) { this.notificationTemplateRepository = notificationTemplateRepository; } diff --git a/WebAPI/LearningHub.Nhs.Services/RatingService.cs b/WebAPI/LearningHub.Nhs.Services/RatingService.cs index ead13ade2..23c96c4fa 100644 --- a/WebAPI/LearningHub.Nhs.Services/RatingService.cs +++ b/WebAPI/LearningHub.Nhs.Services/RatingService.cs @@ -232,7 +232,6 @@ public async Task CreateRating(int userId, RatingVi await this.GetRatingSummaryBasic(ratingViewModel.EntityVersionId); - // TODO: Submit new resource record to findwise. return retVal; } @@ -333,7 +332,6 @@ private async Task CreateNewRatingAndRecalcAverageAsync(int userId, RatingViewMo await this.resourceVersionRatingSummaryRepository.UpdateAsync(userId, ratingSummary); - // Submit updated rating to Findwise. await this.resourceService.SubmitResourceVersionToSearchAsync(ratingViewModel.EntityVersionId, userId); } diff --git a/WebAPI/LearningHub.Nhs.Services/ResourceService.cs b/WebAPI/LearningHub.Nhs.Services/ResourceService.cs index 6d71a5194..30278bad6 100644 --- a/WebAPI/LearningHub.Nhs.Services/ResourceService.cs +++ b/WebAPI/LearningHub.Nhs.Services/ResourceService.cs @@ -984,7 +984,7 @@ public async Task PublishResourceVersionAsync(PublishViewModel publishViewM } /// - /// Submits a published resource version to the Findwise search. + /// Submits a published resource version to search. /// /// The resourceVersionId. /// The userId. @@ -1236,7 +1236,7 @@ public async Task UnpublishResourceVersion(Unpublis this.resourceVersionRepository.Unpublish(unpublishViewModel.ResourceVersionId, unpublishViewModel.Details, userId); - // Remove from Findwise Search Index + // Remove from Search Index var r = await this.resourceVersionRepository.GetBasicByIdAsync(unpublishViewModel.ResourceVersionId); await this.searchService.RemoveResourceFromSearchAsync(r.ResourceId); diff --git a/WebAPI/LearningHub.Nhs.Services/ResourceSyncService.cs b/WebAPI/LearningHub.Nhs.Services/ResourceSyncService.cs index 9c24ff569..9373ad6c8 100644 --- a/WebAPI/LearningHub.Nhs.Services/ResourceSyncService.cs +++ b/WebAPI/LearningHub.Nhs.Services/ResourceSyncService.cs @@ -24,7 +24,6 @@ public class ResourceSyncService : IResourceSyncService private readonly IResourceVersionRepository resourceVersionRepository; private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; private readonly IGenericFileResourceVersionRepository genericFileResourceVersionRepository; - private readonly IFindwiseApiFacade findwiseApiFacade; private readonly IMapper mapper; /// @@ -34,21 +33,18 @@ public class ResourceSyncService : IResourceSyncService /// The resourceVersionRepository. /// The catalogueNodeVersionRepository. /// The genericFileResourceVersionRepository. - /// The findwiseApiFacade. /// The mapper. public ResourceSyncService( IResourceSyncRepository resourceSyncRepository, IResourceVersionRepository resourceVersionRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, - IFindwiseApiFacade findwiseApiFacade, IMapper mapper) { this.resourceSyncRepository = resourceSyncRepository; this.resourceVersionRepository = resourceVersionRepository; this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; this.genericFileResourceVersionRepository = genericFileResourceVersionRepository; - this.findwiseApiFacade = findwiseApiFacade; this.mapper = mapper; } @@ -104,46 +100,6 @@ public List GetSyncListResourcesForUser(int /// The task. public async Task SyncForUserAsync(int userId) { - // Obtain Resource Version dataset related to the sync list for the supplied User Id. - var syncList = this.resourceSyncRepository.GetSyncListForUser(userId, true).ToList(); - - var resources = syncList.Select(x => x.Resource).ToList(); - - // Sync Updates - var resourcesToUpdate = resources.Where(x => !this.ResourceIsUnpublished(x)).ToList(); - var resourceVersionIdList = resourcesToUpdate.Select(x => x.Id).ToList(); - var mappedResourcesToUpdate = await this.BuildSearchResourceRequestModelList(resourceVersionIdList); - - // Validate Findwise submission - var invalidResourceDetails = new List(); - foreach (var r in mappedResourcesToUpdate) - { - if (!r.IsValidForSubmission()) - { - invalidResourceDetails.Add(r.Title); - } - } - - if (invalidResourceDetails.Count > 0) - { - return new LearningHubValidationResult(false, $"The following Resources are not valid for submission: {string.Join(", ", invalidResourceDetails)}"); - } - - // Send update to Findwise - if (mappedResourcesToUpdate.Any()) - { - await this.findwiseApiFacade.AddOrReplaceAsync(mappedResourcesToUpdate.ToList()); - } - - // Synce Deletes - var resourcesToDelete = resources.Where(x => this.ResourceIsUnpublished(x)).ToList(); - var mappedResourcesToDelete = resourcesToDelete.Select(x => this.mapper.Map(x)).ToList(); - - if (mappedResourcesToDelete.Any()) - { - await this.findwiseApiFacade.RemoveAsync(mappedResourcesToDelete.ToList()); - } - await this.resourceSyncRepository.SetSyncedForUserAsync(userId); return new LearningHubValidationResult(true); @@ -247,22 +203,7 @@ public async Task SyncSingleAsync(int resourceVersi if (resourceVersion == null) { throw new Exception($"No resource version found for id '{resourceVersionId}'"); - } - - var searchResourceRequestModel = await this.BuildSearchResourceRequestModel(resourceVersionId); - if (this.ResourceIsUnpublished(resourceVersion)) - { - await this.findwiseApiFacade.RemoveAsync(new List { searchResourceRequestModel }); - } - else - { - if (!searchResourceRequestModel.IsValidForSubmission()) - { - return new LearningHubValidationResult(false, $"Resource '{resourceVersion.Title}' is not valid for submission to Findwise"); - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchResourceRequestModel }); - } + } return new LearningHubValidationResult(true); } diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index d05c73cb5..be5cc8d98 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -42,18 +42,16 @@ public class SearchService : BaseService, ISearchService /// Initializes a new instance of the class. /// The search service. /// - /// The FindWise Http Client. /// The event service. /// The logger. /// The settings. /// The mapper. public SearchService( - IFindWiseHttpClient findWiseHttpClient, IEventService eventService, ILogger logger, IOptions settings, IMapper mapper) - : base(findWiseHttpClient, logger) + : base(logger) { this.eventService = eventService; this.settings = settings.Value; @@ -68,69 +66,8 @@ public SearchService( /// 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.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - var request = string.Format( - this.settings.Findwise.UrlSearchComponent + "?offset={1}&hits={2}&q={3}&token={4}", - this.settings.Findwise.CollectionIds.Resource, - offset, - pageSize, - this.EncodeSearchText(searchRequestModel.SearchText) + searchRequestModel.FilterText + searchRequestModel.ResourceAccessLevelFilterText + searchRequestModel.ProviderFilterText, - this.settings.Findwise.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; - } + this.Logger.LogWarning("Search is not currently configured. Returning empty results."); + return await Task.FromResult(new SearchResultModel()); } /// @@ -147,53 +84,7 @@ public async Task GetSearchResultAsync(SearchRequestModel sea /// 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.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - var request = string.Format( - this.settings.Findwise.UrlSearchComponent + "?offset={1}&hits={2}&q={3}&token={4}", - this.settings.Findwise.CollectionIds.Catalogue, - offset, - catalogSearchRequestModel.PageSize, - this.EncodeSearchText(catalogSearchRequestModel.SearchText), - this.settings.Findwise.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; - } + return await Task.FromResult(new SearchCatalogueResultModel()); } /// @@ -205,53 +96,7 @@ public async Task GetCatalogueSearchResultAsync(Cata /// The . public async Task SendResourceForSearchAsync(SearchResourceRequestModel searchResourceRequestModel, int userId, int? iterations) { - try - { - if (string.IsNullOrEmpty(this.settings.Findwise.IndexMethod)) - { - this.Logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not added to search results"); - } - else - { - List resourceList = new List(); - resourceList.Add(searchResourceRequestModel); - - var json = JsonConvert.SerializeObject(resourceList, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); - - var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.IndexUrl); - - var request = string.Format(this.settings.Findwise.IndexMethod, this.settings.Findwise.CollectionIds.Resource) + $"?token={this.settings.Findwise.Token}"; - var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); - iterations--; - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.Logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - if (iterations < 0) - { - this.Logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("Posting of resource to search failed: " + stringContent); - } - - await this.SendResourceForSearchAsync(searchResourceRequestModel, userId, iterations); - } - else - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - throw new Exception("Posting of resource to search failed: " + searchResourceRequestModel.Id + " : " + ex.Message); - } + return await Task.FromResult(true); } /// @@ -443,36 +288,9 @@ public async Task CreateCatalogueResourceSearchActi /// /// The resource to be removed from search. /// The . - public async Task RemoveResourceFromSearchAsync(int resourceId) + public Task RemoveResourceFromSearchAsync(int resourceId) { - try - { - if (string.IsNullOrEmpty(this.settings.Findwise.IndexMethod)) - { - this.Logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not removed from search."); - } - else - { - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.IndexUrl); - - var request = string.Format(this.settings.Findwise.IndexMethod, this.settings.Findwise.CollectionIds.Resource) + $"?id={resourceId.ToString()}&token={this.settings.Findwise.Token}"; - - var response = await client.DeleteAsync(request).ConfigureAwait(false); - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - throw new Exception("Removal of resource to search failed: " + resourceId.ToString()); - } - } - } - catch (Exception ex) - { - throw new Exception("Removal of resource from search failed: " + resourceId.ToString() + " : " + ex.Message); - } + return Task.CompletedTask; } /// @@ -548,44 +366,7 @@ public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel /// The . public async Task GetAllCatalogueSearchResultsAsync(AllCatalogueSearchRequestModel catalogSearchRequestModel) { - var viewmodel = new SearchAllCatalogueResultModel(); - try - { - var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - var request = string.Format( - this.settings.Findwise.UrlSearchComponent + "?offset={1}&hits={2}&q={3}&token={4}", - this.settings.Findwise.CollectionIds.Catalogue, - offset, - catalogSearchRequestModel.PageSize, - this.EncodeSearchText(catalogSearchRequestModel.SearchText), - this.settings.Findwise.Token); - - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.Logger.LogError($"Get AllCatalogue 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 AllCatalogue 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; - } + return await Task.FromResult(new SearchAllCatalogueResultModel()); } /// @@ -595,42 +376,7 @@ public async Task GetAllCatalogueSearchResultsAsy /// The . public async Task GetAutoSuggestionResultsAsync(string term) { - var viewmodel = new AutoSuggestionModel(); - - try - { - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - var request = string.Format( - this.settings.Findwise.UrlSearchComponent + "?q={1}&token={2}", - this.settings.Findwise.CollectionIds.AutoSuggestion, - this.EncodeSearchText(term), - this.settings.Findwise.Token); - - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.Logger.LogError($"Get Auto Suggetion 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 Auto Suggetion Result failed in FindWise, HTTP Status Code:{response.StatusCode}, Error Message:{error}"); - throw new Exception("Error with FindWise Server"); - } - - return viewmodel; - } - catch (Exception) - { - throw; - } + return await Task.FromResult(new AutoSuggestionModel()); } /// @@ -662,46 +408,7 @@ public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadM /// private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) { - var eventType = isResource ? "resource" : "catalog"; - - try - { - if (string.IsNullOrEmpty(this.settings.Findwise.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.settings.Findwise.UrlClickComponent}?payload={base64EncodedString}"; - - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - 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}"); - } + return await Task.FromResult(false); } /// @@ -713,44 +420,7 @@ private async Task SendSearchEventClickAsync(SearchClickPayloadModel searc /// private async Task SendAutoSuggestionEventClickAsync(AutoSuggestionClickPayloadModel clickPayloadModel) { - try - { - if (string.IsNullOrEmpty(this.settings.Findwise.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.settings.Findwise.UrlAutoSuggestionClickComponent}?payload={base64EncodedString}"; - - var client = await this.FindWiseHttpClient.GetClient(this.settings.Findwise.SearchUrl); - 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}"); - } + return await Task.FromResult(false); } private string EncodeSearchText(string searchText) From c55e1a17f231cb03a0c32827c9e7e74b15c19638 Mon Sep 17 00:00:00 2001 From: Binon Date: Tue, 14 Apr 2026 17:27:59 +0100 Subject: [PATCH 14/24] removed the remaining findwise references in webapi --- .../Controllers/SearchController.cs | 6 +- WebAPI/LearningHub.Nhs.API/appsettings.json | 17 +----- .../Configuration/AzureSearchSettings.cs | 19 ++++++ .../FindwiseCollectionIdSettings.cs | 28 --------- .../Configuration/FindwiseSettings.cs | 58 ------------------- .../Configuration/Settings.cs | 4 +- .../LearningHub.Nhs.Services/SearchService.cs | 6 +- 7 files changed, 29 insertions(+), 109 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Api.Shared/Configuration/AzureSearchSettings.cs delete mode 100644 WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseCollectionIdSettings.cs delete mode 100644 WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseSettings.cs diff --git a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs index ea4d10d04..1e6f72e35 100644 --- a/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs +++ b/WebAPI/LearningHub.Nhs.API/Controllers/SearchController.cs @@ -312,7 +312,7 @@ private async Task GetSearchResults(SearchRequestModel searchRe searchViewModel.DocumentModel = results.DocumentList.Documents.ToList(); searchViewModel.SearchString = searchRequestModel.SearchText; searchViewModel.Hits = results.DocumentList.Documents.Count(); - searchViewModel.DescriptionMaximumLength = this.settings.Findwise.MaximumDescriptionLength; + searchViewModel.DescriptionMaximumLength = this.settings.AzureSearch.MaximumDescriptionLength; searchViewModel.ErrorOnAPI = results.ErrorsOnAPICall; searchViewModel.Facets = results.Facets; @@ -418,7 +418,7 @@ private async Task GetCatalogueSearchResults(Catalogue DocumentModel = documents, SearchString = catalogueSearchRequestModel.SearchText, Hits = results.DocumentList.Documents.Count(), - DescriptionMaximumLength = this.settings.Findwise.MaximumDescriptionLength, + DescriptionMaximumLength = this.settings.AzureSearch.MaximumDescriptionLength, ErrorOnAPI = results.ErrorsOnAPICall, Facets = results.Facets, }; @@ -491,7 +491,7 @@ private async Task GetAllCatalogueResults(AllCatalo DocumentModel = documents, SearchString = catalogueSearchRequestModel.SearchText, Hits = results.DocumentList.Documents.Count(), - DescriptionMaximumLength = this.settings.Findwise.MaximumDescriptionLength, + DescriptionMaximumLength = this.settings.AzureSearch.MaximumDescriptionLength, ErrorOnAPI = results.ErrorsOnAPICall, Facets = results.Facets, }; diff --git a/WebAPI/LearningHub.Nhs.API/appsettings.json b/WebAPI/LearningHub.Nhs.API/appsettings.json index b35cc9fe6..5498a983a 100644 --- a/WebAPI/LearningHub.Nhs.API/appsettings.json +++ b/WebAPI/LearningHub.Nhs.API/appsettings.json @@ -83,21 +83,8 @@ } }, - "FindWise": { - "IndexUrl": "", - "SearchUrl": "", - "CollectionIds": { - "Resource": "", - "Catalogue": "", - "AutoSuggestion": "" - }, - "UrlSearchComponent": "rest/apps/HEE/searchers/{0}", - "UrlClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click", - "UrlAutoSuggestionClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click-hee", - "Token": "", - "MaximumDescriptionLength": 150, - "IndexMethod": "/rest/v2/collections/{0}/documents", - "DescriptionLengthLimit": 3000 + "AzureSearch": { + "MaximumDescriptionLength": 150 }, "Notifications": { "PublishResourceTimeToProcessInSec": 30, diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/AzureSearchSettings.cs b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/AzureSearchSettings.cs new file mode 100644 index 000000000..796306ac7 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/AzureSearchSettings.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.Api.Shared.Configuration +{ + /// + /// The Azure AI Search configuration settings. + /// + public class AzureSearchSettings + { + /// + /// Gets or sets the maximum description length. + /// + public int MaximumDescriptionLength { get; set; } = 150; + } +} diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseCollectionIdSettings.cs b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseCollectionIdSettings.cs deleted file mode 100644 index b83dc6805..000000000 --- a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseCollectionIdSettings.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace LearningHub.Nhs.Api.Shared.Configuration -{ - /// - /// The FindwiseCollectionIdSettings. - /// - public class FindwiseCollectionIdSettings - { - /// - /// Gets or sets the resource collection id. - /// - public string Resource { get; set; } - - /// - /// Gets or sets the catalogue collection id. - /// - public string Catalogue { get; set; } - - /// - /// Gets or sets the AutoSuggestion collection id. - /// - public string AutoSuggestion { get; set; } - - /// - /// Gets or sets the moodle collection id. - /// - public string Moodle { get; set; } - } -} diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseSettings.cs b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseSettings.cs deleted file mode 100644 index aa3802eee..000000000 --- a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/FindwiseSettings.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace LearningHub.Nhs.Api.Shared.Configuration -{ - /// - /// The FindwiseSettings. - /// - public class FindwiseSettings - { - /// - /// Gets or sets the index url. - /// - public string IndexUrl { get; set; } - - /// - /// Gets or sets the search url. - /// - public string SearchUrl { get; set; } - - /// - /// Gets or sets the url search component. - /// - public string UrlSearchComponent { get; set; } - - /// - /// Gets or sets the url click component. - /// - public string UrlClickComponent { get; set; } - - /// - /// Gets or sets the url AutoSuggestion click component. - /// - public string UrlAutoSuggestionClickComponent { get; set; } - - /// - /// Gets or sets the token. - /// - public string Token { get; set; } - - /// - /// Gets or sets the maximum description length. - /// - public int MaximumDescriptionLength { get; set; } - - /// - /// Gets or sets the index method. - /// - public string IndexMethod { get; set; } - - /// - /// Gets or sets the collection ids. - /// - public FindwiseCollectionIdSettings CollectionIds { get; set; } - - /// - /// Gets or sets the Description length limit. - /// - public int DescriptionLengthLimit { get; set; } - } -} diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/Settings.cs b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/Settings.cs index 13b2b0654..544e9a0f0 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/Settings.cs +++ b/WebAPI/LearningHub.Nhs.Api.Shared/Configuration/Settings.cs @@ -133,9 +133,9 @@ public class Settings public MigrationToolSettings MigrationTool { get; set; } /// - /// Gets or sets the findwise settings. + /// Gets or sets the Azure Search settings. /// - public FindwiseSettings Findwise { get; set; } + public AzureSearchSettings AzureSearch { get; set; } /// /// Gets or sets the azure storage account connectionstring for queue. diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index be5cc8d98..3680f28c0 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -334,7 +334,7 @@ public async Task SendResourceSearchEventClickAsync(SearchActionResourceMo searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.settings.Findwise.CollectionIds.Resource; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = "Resource"; return await this.SendSearchEventClickAsync(searchClickPayloadModel, true); } @@ -354,7 +354,7 @@ public async Task SendCatalogueSearchEventAsync(SearchActionCatalogueModel searchClickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); searchClickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.settings.Findwise.CollectionIds.Catalogue; + searchClickPayloadModel.SearchSignal.ProfileSignature.ProfileId = "Catalogue"; return await this.SendSearchEventClickAsync(searchClickPayloadModel, false); } @@ -393,7 +393,7 @@ public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadM clickPayloadModel.TimeOfClick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); clickPayloadModel.SearchSignal.ProfileSignature.ApplicationId = ApplicationId; clickPayloadModel.SearchSignal.ProfileSignature.ProfileType = ProfileType; - clickPayloadModel.SearchSignal.ProfileSignature.ProfileId = this.settings.Findwise.CollectionIds.AutoSuggestion; + clickPayloadModel.SearchSignal.ProfileSignature.ProfileId = "AutoSuggestion"; return await this.SendAutoSuggestionEventClickAsync(clickPayloadModel); } From d3f77100b90c186f9b2c711b7a74ff264f588169 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 16 Apr 2026 10:30:40 +0100 Subject: [PATCH 15/24] TD-7125: LH Admin UI - when category is not assigned to the catalogue and clicked save results in error --- .../Controllers/CatalogueController.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs index 25aa6cd4c..b9f1b5e03 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs @@ -738,20 +738,29 @@ public async Task AddCategoryToCatalogue(CatalogueViewModel catal if (string.IsNullOrEmpty(catalogueViewModel.SelectedCategoryId)) { this.ModelState.AddModelError("SelectedCategoryId", "Please select a category."); - } - var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId); - vm.SelectedCategoryId = catalogueViewModel.SelectedCategoryId; - var vr = await this.catalogueService.AddCategoryToCatalogue(vm); - if (vr.Success) - { + var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId); var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); + return this.View("MoodleCategory", vm); } + else { - this.ViewBag.ErrorMessage = $"Category Update failed."; - return this.View("MoodleCategory", vm); + var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId); + vm.SelectedCategoryId = catalogueViewModel.SelectedCategoryId; + var vr = await this.catalogueService.AddCategoryToCatalogue(vm); + if (vr.Success) + { + var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); + return this.View("MoodleCategory", vm); + } + else + { + this.ViewBag.ErrorMessage = $"Category Update failed."; + return this.View("MoodleCategory", vm); + } } } From 1761847fc670cd3da4d0eef4c67db6cef38db1a0 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 16 Apr 2026 11:50:38 +0100 Subject: [PATCH 16/24] Need to remosve some more dependency in openAPI project. --- .../Services/IFindwiseApiFacade.cs | 37 ----- .../LearningHub.Nhs.OpenApi.Services.csproj | 4 + .../Services/CatalogueService.cs | 76 +--------- .../Services/Findwise/FindwiseApiFacade.cs | 132 ------------------ .../Findwise/NullFindwiseApiFacade.cs | 59 -------- .../Services/ResourceSyncService.cs | 65 --------- .../Startup.cs | 11 -- .../Services/CatalogueServiceTests.cs | 4 +- 8 files changed, 6 insertions(+), 382 deletions(-) delete mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IFindwiseApiFacade.cs delete mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/FindwiseApiFacade.cs delete mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/NullFindwiseApiFacade.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IFindwiseApiFacade.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IFindwiseApiFacade.cs deleted file mode 100644 index dddf22fc1..000000000 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IFindwiseApiFacade.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace LearningHub.Nhs.OpenApi.Services.Interface.Services -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using LearningHub.Nhs.Models.Search; - - /// - /// The IFindwiseApiFacade. - /// - public interface IFindwiseApiFacade - { - /// - /// Modifies the information Findwise has for the catalogues provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The catalogues to add/replace in the index. - /// The task. - Task AddOrReplaceAsync(List catalogues); - - /// - /// Modifies the information Findwise has for the resources provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The resources to add/replace in the index. - /// The task. - Task AddOrReplaceAsync(List resources); - - /// - /// Removes the documents from Findwise. - /// - /// The resources to remove from Findwise. - /// The task. - Task RemoveAsync(List resources); - } -} 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 291dbe784..64b868475 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -39,4 +39,8 @@ + + + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 0dfb757e7..71696c3dd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -51,7 +51,6 @@ public class CatalogueService : ICatalogueService private readonly IBookmarkRepository bookmarkRepository; private readonly INodeRepository nodeRepository; private readonly INodeActivityRepository nodeActivityRepository; - private readonly IFindwiseApiFacade findwiseApiFacade; private readonly LearningHubConfig learningHubConfig; private readonly FindwiseConfig findwiseConfig; private readonly INotificationSenderService notificationSenderService; @@ -78,8 +77,7 @@ public class CatalogueService : ICatalogueService /// /// /// - /// - public CatalogueService(ICatalogueRepository catalogueRepository, ICategoryService categoryService, 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) + public CatalogueService(ICatalogueRepository catalogueRepository, ICategoryService categoryService, 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, INotificationSenderService notificationSenderService, ITimezoneOffsetManager timezoneOffsetManager) { this.catalogueRepository = catalogueRepository; this.categoryService = categoryService; @@ -97,7 +95,6 @@ public CatalogueService(ICatalogueRepository catalogueRepository, ICategoryServi this.emailSenderService = emailSenderService; this.bookmarkRepository = bookmarkRepository; this.nodeActivityRepository = nodeActivityRepository; - this.findwiseApiFacade = findwiseApiFacade; this.learningHubConfig = learningHubConfig.Value; this.findwiseConfig = findwiseConfig.Value; this.timezoneOffsetManager = timezoneOffsetManager; @@ -575,24 +572,6 @@ public async Task CreateCatalogueAsync(int userId, var catalogueNodeVersionId = await this.catalogueNodeVersionRepository.CreateCatalogueAsync(userId, catalogue); - // Catalogue is in database, push to findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.Id == catalogueNodeVersionId); - - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.findwiseConfig.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.findwiseConfig.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true) { CreatedId = catalogueNodeVersionId, @@ -809,24 +788,6 @@ public async Task UpdateCatalogueAsync(int userId, await this.catalogueNodeVersionRepository.UpdateCatalogueAsync(userId, catalogue); - // Update catalogue in findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.Id == catalogue.CatalogueNodeVersionId); - - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.findwiseConfig.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.findwiseConfig.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true); } @@ -853,24 +814,6 @@ public async Task ShowCatalogueAsync(int userId, in await this.catalogueNodeVersionRepository.ShowCatalogue(userId, nodeId); - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.NodeVersion.NodeId == nodeId); - - // update findwise - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.findwiseConfig.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.findwiseConfig.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } - return new LearningHubValidationResult(true); } @@ -927,23 +870,6 @@ public async Task HideCatalogueAsync(int userId, int nodeId) node.Hidden = true; await this.nodeRepository.UpdateAsync(userId, node); - - // update findwise - var cnv = this.catalogueNodeVersionRepository.GetAll() - .Include(x => x.NodeVersion).ThenInclude(x => x.Node) - .Include(x => x.Keywords) - .Include(x => x.CatalogueNodeVersionProvider) - .SingleOrDefault(x => x.NodeVersion.NodeId == nodeId); - if (cnv != null) - { - var searchModel = this.mapper.Map(cnv); - if (searchModel.Description.Length > this.findwiseConfig.DescriptionLengthLimit) - { - searchModel.Description = searchModel.Description.Substring(0, this.findwiseConfig.DescriptionLengthLimit - 4) + "

    "; - } - - await this.findwiseApiFacade.AddOrReplaceAsync(new List { searchModel }); - } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/FindwiseApiFacade.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/FindwiseApiFacade.cs deleted file mode 100644 index 139c9e41c..000000000 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/FindwiseApiFacade.cs +++ /dev/null @@ -1,132 +0,0 @@ -namespace LearningHub.Nhs.OpenApi.Services.Services.Findwise -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading.Tasks; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.OpenApi.Models.Configuration; - using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; - using LearningHub.Nhs.OpenApi.Services.Interface.Services; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; - - /// - /// The FindwiseApiFacade. - /// - public class FindwiseApiFacade : IFindwiseApiFacade - { - private readonly IFindwiseClient findWiseHttpClient; - private readonly FindwiseConfig findwiseConfig; - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// The findWiseHttpClient. - /// The options. - /// The logger. - public FindwiseApiFacade( - IFindwiseClient findWiseHttpClient, - IOptions findwiseConfig, - ILogger logger) - { - this.findWiseHttpClient = findWiseHttpClient; - this.findwiseConfig = findwiseConfig.Value; - this.logger = logger; - } - - /// - /// Modifies the information Findwise has for the catalogues provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The catalogues to add/replace in the index. - /// The task. - public async Task AddOrReplaceAsync(List catalogues) - { - var request = string.Format(this.findwiseConfig.IndexMethod, this.findwiseConfig.CollectionIds.Catalogue) - + $"?token={this.findwiseConfig.Token}"; - var response = await this.PostAsync(request, catalogues); - this.ValidateResponse(response, $"catalogues: {string.Join(',', catalogues.Select(x => x.Id))}"); - } - - /// - /// Modifies the information Findwise has for the resources provided. - /// Documents not in Findwise will be added. - /// Documents that already exist in Findwise will be replaced. - /// - /// The resources to add/replace in the index. - /// The task. - public async Task AddOrReplaceAsync(List resources) - { - var request = string.Format(this.findwiseConfig.IndexMethod, this.findwiseConfig.CollectionIds.Resource) - + $"?token={this.findwiseConfig.Token}"; - var response = await this.PostAsync(request, resources); - this.ValidateResponse(response, $"resources: {string.Join(',', resources.Select(x => x.Id))}"); - } - - /// - /// Removes the documents from Findwise. - /// - /// The resources to remove from Findwise. - /// The task. - public async Task RemoveAsync(List resources) - { - var resourceIds = resources.Select(x => x.Id); - var idQueryString = string.Join( - '&', - resourceIds.Select(x => $"id={x}")); - var request = string.Format( - this.findwiseConfig.IndexMethod, - this.findwiseConfig.CollectionIds.Resource) - + $"?{idQueryString}&token={this.findwiseConfig.Token}"; - var response = await this.DeleteAsync(request); - this.ValidateResponse(response, $"resources: {string.Join(',', resourceIds)}"); - } - - private void ValidateResponse(HttpResponseMessage response, string dataForError) - { - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - logger.LogError($"Updating FindWise failed for {dataForError} HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - logger.LogError($"Updating FindWise failed for {dataForError} HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("Posting of resource to search failed: " + dataForError); - } - } - - private async Task PostAsync(string request, T obj) - where T : class, new() - { - var content = GetContent(obj); - var client = await GetClientAsync(); - return await client.PostAsync(request, content).ConfigureAwait(false); - } - - private async Task DeleteAsync(string request) - { - var client = await GetClientAsync(); - return await client.DeleteAsync(request).ConfigureAwait(false); - } - - private async Task GetClientAsync() - { - return await this.findWiseHttpClient.GetClient(this.findwiseConfig.IndexUrl); - } - - private StringContent GetContent(T obj) - where T : class, new() - { - var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); - - return new StringContent(json, Encoding.UTF8, "application/json"); - } - } -} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/NullFindwiseApiFacade.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/NullFindwiseApiFacade.cs deleted file mode 100644 index ef5f8d974..000000000 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/Findwise/NullFindwiseApiFacade.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace LearningHub.Nhs.OpenApi.Services.Services.Findwise -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.OpenApi.Services.Interface.Services; - using Microsoft.Extensions.Logging; - - /// - /// Null implementation of IFindwiseApiFacade for use when Azure Search is enabled. - /// This implementation performs no operations and is used to avoid Findwise calls when using Azure Search. - /// - public class NullFindwiseApiFacade : IFindwiseApiFacade - { - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - public NullFindwiseApiFacade(ILogger logger) - { - this.logger = logger; - } - - /// - /// No-op implementation. Does not add or replace catalogues in Findwise. - /// - /// The catalogues to add/replace in the index. - /// The task. - public Task AddOrReplaceAsync(List catalogues) - { - this.logger.LogDebug("NullFindwiseApiFacade: Skipping AddOrReplaceAsync for {Count} catalogues (Azure Search is enabled)", catalogues?.Count ?? 0); - return Task.CompletedTask; - } - - /// - /// No-op implementation. Does not add or replace resources in Findwise. - /// - /// The resources to add/replace in the index. - /// The task. - public Task AddOrReplaceAsync(List resources) - { - this.logger.LogDebug("NullFindwiseApiFacade: Skipping AddOrReplaceAsync for {Count} resources (Azure Search is enabled)", resources?.Count ?? 0); - return Task.CompletedTask; - } - - /// - /// No-op implementation. Does not remove resources from Findwise. - /// - /// The resources to remove from Findwise. - /// The task. - public Task RemoveAsync(List resources) - { - this.logger.LogDebug("NullFindwiseApiFacade: Skipping RemoveAsync for {Count} resources (Azure Search is enabled)", resources?.Count ?? 0); - return Task.CompletedTask; - } - } -} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs index ba6e4b243..921f04d7b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs @@ -24,7 +24,6 @@ public class ResourceSyncService : IResourceSyncService private readonly IResourceVersionRepository resourceVersionRepository; private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; private readonly IGenericFileResourceVersionRepository genericFileResourceVersionRepository; - private readonly IFindwiseApiFacade findwiseApiFacade; private readonly IMapper mapper; /// @@ -34,21 +33,18 @@ public class ResourceSyncService : IResourceSyncService /// The resourceVersionRepository. /// The catalogueNodeVersionRepository. /// The genericFileResourceVersionRepository. - /// The findwiseApiFacade. /// The mapper. public ResourceSyncService( IResourceSyncRepository resourceSyncRepository, IResourceVersionRepository resourceVersionRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, - IFindwiseApiFacade findwiseApiFacade, IMapper mapper) { this.resourceSyncRepository = resourceSyncRepository; this.resourceVersionRepository = resourceVersionRepository; this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; this.genericFileResourceVersionRepository = genericFileResourceVersionRepository; - this.findwiseApiFacade = findwiseApiFacade; this.mapper = mapper; } @@ -104,46 +100,6 @@ public List GetSyncListResourcesForUser(int /// The task. public async Task SyncForUserAsync(int userId) { - // Obtain Resource Version dataset related to the sync list for the supplied User Id. - var syncList = resourceSyncRepository.GetSyncListForUser(userId, true).ToList(); - - var resources = syncList.Select(x => x.Resource).ToList(); - - // Sync Updates - var resourcesToUpdate = resources.Where(x => !this.ResourceIsUnpublished(x)).ToList(); - var resourceVersionIdList = resourcesToUpdate.Select(x => x.Id).ToList(); - var mappedResourcesToUpdate = await this.BuildSearchResourceRequestModelList(resourceVersionIdList); - - // Validate Findwise submission - var invalidResourceDetails = new List(); - foreach (var r in mappedResourcesToUpdate) - { - if (!r.IsValidForSubmission()) - { - invalidResourceDetails.Add(r.Title); - } - } - - if (invalidResourceDetails.Count > 0) - { - return new LearningHubValidationResult(false, $"The following Resources are not valid for submission: {string.Join(", ", invalidResourceDetails)}"); - } - - // Send update to Findwise - if (mappedResourcesToUpdate.Any()) - { - await findwiseApiFacade.AddOrReplaceAsync(mappedResourcesToUpdate.ToList()); - } - - // Synce Deletes - var resourcesToDelete = resources.Where(x => this.ResourceIsUnpublished(x)).ToList(); - var mappedResourcesToDelete = resourcesToDelete.Select(x => mapper.Map(x)).ToList(); - - if (mappedResourcesToDelete.Any()) - { - await findwiseApiFacade.RemoveAsync(mappedResourcesToDelete.ToList()); - } - await resourceSyncRepository.SetSyncedForUserAsync(userId); return new LearningHubValidationResult(true); @@ -243,27 +199,6 @@ public async Task BuildSearchResourceRequestModel(in /// The task. public async Task SyncSingleAsync(int resourceVersionId) { - var resourceVersion = GetResourcesWithIncludes().SingleOrDefault(x => x.Id == resourceVersionId); - if (resourceVersion == null) - { - throw new Exception($"No resource version found for id '{resourceVersionId}'"); - } - - var searchResourceRequestModel = await BuildSearchResourceRequestModel(resourceVersionId); - if (ResourceIsUnpublished(resourceVersion)) - { - await findwiseApiFacade.RemoveAsync(new List { searchResourceRequestModel }); - } - else - { - if (!searchResourceRequestModel.IsValidForSubmission()) - { - return new LearningHubValidationResult(false, $"Resource '{resourceVersion.Title}' is not valid for submission to Findwise"); - } - - await findwiseApiFacade.AddOrReplaceAsync(new List { searchResourceRequestModel }); - } - return new LearningHubValidationResult(true); } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index 1670bc090..c2e0ae594 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -7,7 +7,6 @@ namespace LearningHub.Nhs.OpenApi.Services using LearningHub.Nhs.OpenApi.Services.Interface.Services.Messaging; using LearningHub.Nhs.OpenApi.Services.Services; using LearningHub.Nhs.OpenApi.Services.Services.AzureSearch; - using LearningHub.Nhs.OpenApi.Services.Services.Findwise; using LearningHub.Nhs.OpenApi.Services.Services.Messaging; using LearningHub.Nhs.Services; using Microsoft.Extensions.Configuration; @@ -93,16 +92,6 @@ public static void AddServices(this IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); services.AddScoped(); - - // Register IFindwiseApiFacade based on feature flag - if (useAzureSearch) - { - services.AddScoped(); - } - else - { - services.AddScoped(); - } } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs index 29e37935a..8eec10b9d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs @@ -38,7 +38,6 @@ public class CatalogueServiceTests private readonly Mock bookmarkRepository; private readonly Mock nodeRepository; private readonly Mock nodeActivityRepository; - private readonly Mock findwiseApiFacade; private readonly Mock> learningHubConfig; private readonly Mock> findwiseConfig; private readonly Mock notificationSenderService; @@ -65,13 +64,12 @@ public CatalogueServiceTests() this.bookmarkRepository = new Mock(); this.nodeRepository = new Mock(); this.nodeActivityRepository = new Mock(); - this.findwiseApiFacade = new Mock(); this.learningHubConfig = new Mock>(); this.findwiseConfig = new Mock>(); this.notificationSenderService = new Mock(); this.timezoneOffsetManager = new Mock(); this.categoryService = new Mock(); - this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.categoryService.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); + this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.categoryService.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.notificationSenderService.Object,this.timezoneOffsetManager.Object); } private static IEnumerable CatalogueNodeVersionList => new List() From ef29bbed0b9a0f8b04a767a0e4c70cfd99092482 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 16 Apr 2026 14:55:34 +0100 Subject: [PATCH 17/24] TD-7126: My courses and learning - elearning resources doesn't appear in In Progress elearning tab --- .../Services/DashboardService.cs | 42 ++++++++++++------- .../Services/MyLearningService.cs | 6 +-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs index bd98ac5b2..c74775001 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -19,6 +19,8 @@ using LearningHub.Nhs.OpenApi.Services.Interface.Services; using LearningHub.Nhs.Models.Entities.Resource; using System.Diagnostics; + using StackExchange.Redis; + using LearningHub.Nhs.Models.Moodle; /// /// The DashboardService. @@ -277,7 +279,8 @@ public async Task GetMyInprogressLearning if (entrolledCourses != null) { - mappedEnrolledCourses = entrolledCourses?.Results? + var courses = entrolledCourses?.Results ?? Enumerable.Empty(); + mappedEnrolledCourses = courses .Where(r => r.Data?.Courses != null) .SelectMany(r => r.Data.Courses) .Select(course => new MyLearningCombinedActivitiesViewModel @@ -312,24 +315,31 @@ public async Task GetMyInprogressLearning : DateTimeOffset.MinValue, }).ToList(); } + try + { + // Combine both result sets + var combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); + int skip = (pageNumber - 1) * 3; + var totalCount = combainedUserActivities.Count() > 8 ? 8 : combainedUserActivities.Count(); - // Combine both result sets - var combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); - int skip = (pageNumber - 1) * 3; - var totalCount = combainedUserActivities.Count() > 8 ? 8 : combainedUserActivities.Count(); + bool isLastPage = skip + 3 >= 8; + int pageSize = isLastPage ? 2 : 3; + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(skip).Take(pageSize).ToList(); - bool isLastPage = skip + 3 >= 8; - int pageSize = isLastPage ? 2 : 3; - var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(skip).Take(pageSize).ToList(); + // Count total records. + MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() + { + TotalCount = totalCount, + Activities = pagedResults, + }; - // Count total records. - MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() + return viewModel; + } + catch (Exception ex) { - TotalCount = totalCount, - Activities = pagedResults, - }; + return null; + } - return viewModel; } /// @@ -357,7 +367,7 @@ public async Task GetUserCertificateDet } // Await all active tasks in parallel - if (courseCertificatesTask != null & dashboardTrayLearningResourceType == "all") + if (courseCertificatesTask != null && dashboardTrayLearningResourceType == "all") await Task.WhenAll(courseCertificatesTask, resourceCertificatesTask); else if (dashboardTrayLearningResourceType == "elearning") await resourceCertificatesTask; @@ -390,7 +400,7 @@ public async Task GetUserCertificateDet CertificateDownloadUrl = c.DownloadLink, ResourceVersionId = 0, ProvidersJson = null - }); + }) ?? Enumerable.Empty(); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs index 7ff1bba1a..8d1793650 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MyLearningService.cs @@ -250,7 +250,7 @@ public async Task GetUserRecentMyLearning CertificateAwardedDate = course.EndDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) : DateTimeOffset.MinValue, - }).ToList(); + }).ToList()?? new List(); ; } // Combine both result sets @@ -397,7 +397,7 @@ public async Task GetUserLearningHistoryA CertificateAwardedDate = course.EndDate.HasValue ? DateTimeOffset.FromUnixTimeSeconds(course.EndDate.Value) : DateTimeOffset.MinValue, - }).ToList(); + }).ToList()?? new List(); } } @@ -763,7 +763,7 @@ public async Task GetUserCertificateDet CertificateDownloadUrl = c.DownloadLink, ResourceVersionId = 0, ProvidersJson = null, - }); + }) ?? Enumerable.Empty(); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); From 5a3148afa829e4fd48580d5c85f4fe4b82a4aaad Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 16 Apr 2026 14:59:40 +0100 Subject: [PATCH 18/24] Removed unwanted lines --- .../Services/DashboardService.cs | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs index c74775001..95dd23552 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -8,19 +8,15 @@ using AutoMapper; using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Moodle; using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Models.Provider; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; - using LearningHub.Nhs.Models.Provider; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; - using LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity; using LearningHub.Nhs.OpenApi.Services.Interface.Services; - using LearningHub.Nhs.Models.Entities.Resource; - using System.Diagnostics; - using StackExchange.Redis; - using LearningHub.Nhs.Models.Moodle; /// /// The DashboardService. @@ -315,31 +311,24 @@ public async Task GetMyInprogressLearning : DateTimeOffset.MinValue, }).ToList(); } - try - { - // Combine both result sets - var combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); - int skip = (pageNumber - 1) * 3; - var totalCount = combainedUserActivities.Count() > 8 ? 8 : combainedUserActivities.Count(); - bool isLastPage = skip + 3 >= 8; - int pageSize = isLastPage ? 2 : 3; - var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(skip).Take(pageSize).ToList(); + // Combine both result sets + var combainedUserActivities = mappedMyLearningActivities.Concat(mappedEnrolledCourses).ToList(); + int skip = (pageNumber - 1) * 3; + var totalCount = combainedUserActivities.Count() > 8 ? 8 : combainedUserActivities.Count(); - // Count total records. - MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() - { - TotalCount = totalCount, - Activities = pagedResults, - }; + bool isLastPage = skip + 3 >= 8; + int pageSize = isLastPage ? 2 : 3; + var pagedResults = combainedUserActivities.OrderByDescending(activity => activity.ActivityDate).Skip(skip).Take(pageSize).ToList(); - return viewModel; - } - catch (Exception ex) + // Count total records. + MyLearningActivitiesDetailedViewModel viewModel = new MyLearningActivitiesDetailedViewModel() { - return null; - } + TotalCount = totalCount, + Activities = pagedResults, + }; + return viewModel; } /// From 304425cae9b22882b9c480e2374f1b070d68714e Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 16 Apr 2026 16:40:42 +0100 Subject: [PATCH 19/24] fixed the issue which was always returning false --- WebAPI/LearningHub.Nhs.Services/SearchService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Services/SearchService.cs b/WebAPI/LearningHub.Nhs.Services/SearchService.cs index 3680f28c0..993448a3a 100644 --- a/WebAPI/LearningHub.Nhs.Services/SearchService.cs +++ b/WebAPI/LearningHub.Nhs.Services/SearchService.cs @@ -408,7 +408,7 @@ public async Task SendAutoSuggestionEventAsync(AutoSuggestionClickPayloadM /// private async Task SendSearchEventClickAsync(SearchClickPayloadModel searchClickPayloadModel, bool isResource) { - return await Task.FromResult(false); + return await Task.FromResult(true); } /// @@ -420,7 +420,7 @@ private async Task SendSearchEventClickAsync(SearchClickPayloadModel searc /// private async Task SendAutoSuggestionEventClickAsync(AutoSuggestionClickPayloadModel clickPayloadModel) { - return await Task.FromResult(false); + return await Task.FromResult(true); } private string EncodeSearchText(string searchText) From dc6a8333ce133ee9b43895f91a038568ad63ac74 Mon Sep 17 00:00:00 2001 From: Binon Date: Thu, 16 Apr 2026 16:50:07 +0100 Subject: [PATCH 20/24] Remove more dependencies --- .../Services/SearchService.cs | 924 ------------------ .../Startup.cs | 3 +- .../Services/Services/SearchServiceTests.cs | 20 +- 3 files changed, 14 insertions(+), 933 deletions(-) delete mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs deleted file mode 100644 index d3eb98d67..000000000 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ /dev/null @@ -1,924 +0,0 @@ -namespace LearningHub.Nhs.OpenApi.Services.Services -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading; - 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; - using LearningHub.Nhs.OpenApi.Models.ServiceModels.Resource; - using LearningHub.Nhs.OpenApi.Models.ViewModels; - using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; - using LearningHub.Nhs.OpenApi.Services.Helpers; - using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; - using LearningHub.Nhs.OpenApi.Services.Interface.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. - /// The search service. - /// - /// - /// The . - /// - /// - /// The . - /// - /// - /// The . - /// - /// - /// The . - /// - /// Logger. - public SearchService( - ILearningHubService learningHubService, - IEventService eventService, - IFindwiseClient findwiseClient, - IOptions findwiseConfig, - IResourceRepository resourceRepository, - 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. - /// Cancellation token. - /// The . - public async Task GetSearchResultAsync(SearchRequestModel searchRequestModel, int userId, CancellationToken cancellationToken = default) - { - 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 + "{0}?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. - /// - /// Cancellation token. - /// - /// The . - /// - public async Task GetCatalogueSearchResultAsync(CatalogueSearchRequestModel catalogSearchRequestModel, int userId, CancellationToken cancellationToken = default) - { - 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 + "{0}?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) - { - var findwiseResultModel = await this.findwiseClient.Search(query); - - if (findwiseResultModel.FindwiseRequestStatus != FindwiseRequestStatus.Success) - { - return ResourceSearchResultModel.FailedWithStatus(findwiseResultModel.FindwiseRequestStatus); - } - - var resourceMetadataViewModels = await this.GetResourceMetadataViewModels(findwiseResultModel, currentUserId); - - var totalHits = findwiseResultModel.SearchResults?.Stats.TotalHits; - - return new ResourceSearchResultModel( - resourceMetadataViewModels, - findwiseResultModel.FindwiseRequestStatus, - totalHits ?? 0); - } - - /// - /// The remove resource from search async method. - /// - /// The resource to be removed from search. - /// The . - public async Task RemoveResourceFromSearchAsync(int resourceId) - { - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.IndexMethod)) - { - this.logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not removed from search."); - } - else - { - var client = await this.findwiseClient.GetClient(this.findwiseConfig.IndexUrl); - - var request = string.Format(this.findwiseConfig.IndexMethod, this.findwiseConfig.CollectionIds.Resource) + $"?id={resourceId.ToString()}&token={this.findwiseConfig.Token}"; - - var response = await client.DeleteAsync(request).ConfigureAwait(false); - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - throw new Exception("Removal of resource to search failed: " + resourceId.ToString()); - } - } - } - catch (Exception ex) - { - throw new Exception("Removal of resource from search failed: " + resourceId.ToString() + " : " + ex.Message); - } - } - - /// - /// The send resource for search Async method. - /// - /// The resource to be added to search. - /// The user id. - /// number of iterations. - /// The . - public async Task SendResourceForSearchAsync(SearchResourceRequestModel searchResourceRequestModel, int userId, int? iterations) - { - try - { - if (string.IsNullOrEmpty(this.findwiseConfig.IndexMethod)) - { - this.logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not added to search results"); - } - else - { - List resourceList =[searchResourceRequestModel]; - - var json = JsonConvert.SerializeObject(resourceList, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); - - var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); - var client = await this.findwiseClient.GetClient(this.findwiseConfig.IndexUrl); - - var request = string.Format(this.findwiseConfig.SearchBaseUrl, this.findwiseConfig.CollectionIds.Resource) + $"?token={this.findwiseConfig.Token}"; - var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); - iterations--; - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("AccessDenied"); - } - else if (!response.IsSuccessStatusCode) - { - if (iterations < 0) - { - this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); - throw new Exception("Posting of resource to search failed: " + stringContent); - } - - await this.SendResourceForSearchAsync(searchResourceRequestModel, userId, iterations); - } - else - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - throw new Exception("Posting of resource to search failed: " + searchResourceRequestModel.Id + " : " + ex.Message); - } - } - - /// - /// 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. - /// - /// The allcatalog search request model. - /// The . - public async Task GetAllCatalogueSearchResultsAsync(AllCatalogueSearchRequestModel catalogSearchRequestModel) - { - var viewmodel = new SearchAllCatalogueResultModel(); - try - { - var offset = catalogSearchRequestModel.PageIndex * catalogSearchRequestModel.PageSize; - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?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); - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Get AllCatalogue 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 AllCatalogue 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 Auto suggestion Results Async method. - /// - /// The term. - /// Cancellation token. - /// The . - public async Task GetAutoSuggestionResultsAsync(string term, CancellationToken cancellationToken = default) - { - var viewmodel = new AutoSuggestionModel(); - - try - { - var client = await this.findwiseClient.GetClient(this.findwiseConfig.SearchBaseUrl); - var request = string.Format( - this.findwiseConfig.SearchEndpointPath + "{0}?q={1}&token={2}", - this.findwiseConfig.CollectionIds.AutoSuggestion, - this.EncodeSearchText(term), - this.findwiseConfig.Token); - - - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = response.Content.ReadAsStringAsync().Result; - viewmodel = JsonConvert.DeserializeObject(result); - } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) - { - this.logger.LogError($"Get Auto Suggetion 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 Auto Suggetion 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 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; - - // Add backslash to the start of the string - specialSearchCharacters = specialSearchCharacters.Replace(@"\", null); - specialSearchCharacters = @"\" + specialSearchCharacters; - - for (int i = 0; i < specialSearchCharacters.Length; i++) - { - searchText = searchText.Replace(specialSearchCharacters[i].ToString(), @"\" + specialSearchCharacters[i]); - } - - searchText = HttpUtility.UrlEncode(searchText); - return searchText; - } - - private async Task> GetResourceMetadataViewModels( - FindwiseResultModel findwiseResultModel, int? currentUserId) - { - List resourceActivities = new List() { }; - List resourceMetadataViewModels = new List() { }; - var documentsFound = findwiseResultModel.SearchResults?.DocumentList.Documents?.ToList() ?? - new List(); - var findwiseResourceIds = documentsFound.Select(d => int.Parse(d.Id)).ToList(); - - if (!findwiseResourceIds.Any()) - { - return new List(); - } - - var resourcesFound = await this.resourceRepository.GetResourcesFromIds(findwiseResourceIds); - - if (currentUserId.HasValue) - { - List resourceIds = resourcesFound.Select(x => x.Id).ToList(); - List userIds = new List() { currentUserId.Value }; - - resourceActivities = (await this.resourceRepository.GetResourceActivityPerResourceMajorVersion(resourceIds, userIds))?.ToList() ?? new List() { }; - } - - resourceMetadataViewModels = resourcesFound.Select(resource => this.MapToViewModel(resource, resourceActivities.Where(x => x.ResourceId == resource.Id).ToList())) - .OrderBySequence(findwiseResourceIds) - .ToList(); - - var unmatchedResources = findwiseResourceIds - .Except(resourceMetadataViewModels.Select(r => r.ResourceId)).ToList(); - - if (unmatchedResources.Any()) - { - var unmatchedResourcesIdsString = string.Join(", ", unmatchedResources); - this.logger.LogWarning( - "Findwise returned documents that were not found in the database with IDs: " + - unmatchedResourcesIdsString); - } - - return resourceMetadataViewModels; - } - - private ResourceMetadataViewModel MapToViewModel(Resource resource, List resourceActivities) - { - var hasCurrentResourceVersion = resource.CurrentResourceVersion != null; - var hasRating = resource.CurrentResourceVersion?.ResourceVersionRatingSummary != null; - - List majorVersionIdActivityStatusDescription = new List() { }; - - if (resourceActivities != null && resourceActivities.Count != 0) - { - majorVersionIdActivityStatusDescription = ActivityStatusHelper.GetMajorVersionIdActivityStatusDescriptionLSPerResource(resource, resourceActivities) - .ToList(); - } - - if (!hasCurrentResourceVersion) - { - this.logger.LogInformation( - $"Resource with id {resource.Id} is missing a current resource version"); - } - - if (!hasRating) - { - this.logger.LogInformation( - $"Resource with id {resource.Id} is missing a ResourceVersionRatingSummary"); - } - - var resourceTypeNameOrEmpty = resource.GetResourceTypeNameOrEmpty(); - if (resourceTypeNameOrEmpty == string.Empty) - { - this.logger.LogError($"Resource has unrecognised type: {resource.ResourceTypeEnum}"); - } - - - return new ResourceMetadataViewModel( - resource.Id, - resource.CurrentResourceVersion?.Title ?? ResourceHelpers.NoResourceVersionText, - resource.CurrentResourceVersion?.Description ?? string.Empty, - resource.ResourceReference.Select(this.GetResourceReferenceViewModel).ToList(), - resourceTypeNameOrEmpty, - resource.CurrentResourceVersion?.MajorVersion ?? 0, - resource.CurrentResourceVersion?.ResourceVersionRatingSummary?.AverageRating ?? 0.0m, - majorVersionIdActivityStatusDescription - ); - } - - private ResourceReferenceViewModel GetResourceReferenceViewModel( - ResourceReference resourceReference) - { - return new ResourceReferenceViewModel( - resourceReference.OriginalResourceReferenceId, - resourceReference.GetCatalogue(), - this.learningHubService.GetResourceLaunchUrl(resourceReference.OriginalResourceReferenceId)); - } - } -} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index c2e0ae594..56b9450ef 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -33,8 +33,7 @@ public static void AddServices(this IServiceCollection services, IConfiguration } else { - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } services.AddHttpClient(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs index d8d0568b2..bc4f1407c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/SearchServiceTests.cs @@ -20,6 +20,7 @@ namespace LearningHub.Nhs.OpenApi.Tests.Services.Services using LearningHub.Nhs.OpenApi.Services.Interface.HttpClients; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using LearningHub.Nhs.OpenApi.Services.Services; + using LearningHub.Nhs.OpenApi.Services.Services.AzureSearch; using LearningHub.Nhs.OpenApi.Tests.TestHelpers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -33,11 +34,13 @@ public class SearchServiceTests private readonly Mock learningHubService; private readonly Mock resourceRepository; private readonly Mock resourceService; + private readonly Mock cachingService; private readonly Mock eventService; - private readonly SearchService searchService; + private readonly AzureSearchService searchService; private readonly Mock> findwiseConfig; - private Mock> mockLogger; + private Mock> mockLogger; private readonly Mock mapper; + private readonly Mock> azureSearchConfig; public SearchServiceTests() { @@ -45,19 +48,22 @@ public SearchServiceTests() this.learningHubService = new Mock(); this.resourceRepository = new Mock(); this.resourceService = new Mock(); + this.cachingService = new Mock(); + this.azureSearchConfig = new Mock>(); this.eventService = new Mock(); this.mapper = new Mock(); this.findwiseConfig = new Mock>(); - this.mockLogger = new Mock>(); - this.searchService = new SearchService( + this.mockLogger = new Mock>(); + this.searchService = new AzureSearchService( this.learningHubService.Object, this.eventService.Object, - this.findwiseClient.Object, - this.findwiseConfig.Object, + this.azureSearchConfig.Object, this.resourceRepository.Object, + this.cachingService.Object, this.mockLogger.Object, - this.mapper.Object); + this.mapper.Object + ); } public static IEnumerable TestFindwiseResultModel => new[] From d2a574028b46f9e73680af050d8347cb35eb77a3 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Fri, 17 Apr 2026 10:09:41 +0100 Subject: [PATCH 21/24] activate CT if off --- .../LearningHub.Nhs.Database.sqlproj | 1 + .../Scripts/Pre-Deploy/Script.PreDeployment.sql | 1 + .../Pre-Deploy/Scripts/ActivateChangeTracking.sql | 10 ++++++++++ 3 files changed, 12 insertions(+) create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Scripts/ActivateChangeTracking.sql diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index f9b533c16..5b4022af9 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -569,6 +569,7 @@ + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Script.PreDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Script.PreDeployment.sql index 52e090048..684201612 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Script.PreDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Script.PreDeployment.sql @@ -18,3 +18,4 @@ BEGIN RAISERROR (N'TD-2902 Add resource types to Content Server.sql must be run manually before release.', 16, 127) WITH NOWAIT END GO +:r .\Scripts\ActivateChangeTracking.sql diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Scripts/ActivateChangeTracking.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Scripts/ActivateChangeTracking.sql new file mode 100644 index 000000000..7e14ea5bd --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Pre-Deploy/Scripts/ActivateChangeTracking.sql @@ -0,0 +1,10 @@ +IF NOT EXISTS ( + SELECT 1 + FROM sys.change_tracking_databases + WHERE database_id = DB_ID() +) +BEGIN + ALTER DATABASE CURRENT + SET CHANGE_TRACKING = ON + (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON); +END \ No newline at end of file From 27230c7e2b3fdcf33cb15a7d6c523b44c3055c54 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Fri, 17 Apr 2026 12:27:27 +0100 Subject: [PATCH 22/24] TD-7126: Issue with the recent learning --- .../LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs | 1 + .../Services/MoodleBridgeApiService.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs index b9f1b5e03..49aef9634 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/CatalogueController.cs @@ -740,6 +740,7 @@ public async Task AddCategoryToCatalogue(CatalogueViewModel catal this.ModelState.AddModelError("SelectedCategoryId", "Please select a category."); var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId); var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + vm.SelectedCategoryId = null; vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); return this.View("MoodleCategory", vm); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs index 80dd76170..71076c3f9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs @@ -215,8 +215,7 @@ public async Task GetRecentEnrolledCoursesAsy } catch (Exception ex) { - this.logger.LogError(ex, "An error occurred while fetching user's recent learning activities "); - throw; + return null; } } From 7c0ab3621350228cc7672e1689221ef9c0623db1 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Fri, 17 Apr 2026 13:10:15 +0100 Subject: [PATCH 23/24] CT reactivation on tables --- .../Scripts/TD-7106-Resume-Databricks-Ingestion.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql index 710a7f897..5dcc36adb 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql @@ -7,9 +7,9 @@ BEGIN PRINT 'Change Tracking is enabled. Executing setup...'; EXEC dbo.lakeflowSetupChangeTracking - @Tables = 'activity.ResourceActivity', --(to include all other tables with CT enabled) - @User = 'Elfhadmin', - @Retention = '2 DAYS'; + @Tables = 'activity.ResourceActivity,resources.VersionStatus,resources.ResourceType,activity.ScormActivity,activity.ActivityStatus,activity.ScormActivityInteraction,activity.ScormActivityInteractionCorrectResponse,resources.VideoResourceVersion,hierarchy.CatalogueNodeVersionProvider,resources.ResourceVersionKeyword,hierarchy.CatalogueNodeVersion,activity.MediaResourceActivity,resources.ResourceVersionAuthor,resources.ResourceVersionEvent,hub.UserProvider,resources.ResourceAccessibility,hierarchy.NodePath,resources.ResourceVersionRating,resources.ResourceVersionEventType,hierarchy.NodeType,hub.User,hierarchy.NodePathNode,hierarchy.VersionStatus,hub.UserProfile,hierarchy.NodeVersion,hierarchy.Publication,hub.Attribute,hierarchy.NodeResource,hierarchy.NodeLink,resources.Resource,hub.RoleUserGroup,hierarchy.CatalogueNodeVersionKeyword,hub.UserGroup,hub.UserUserGroup,resources.WebLinkResourceVersion,resources.ResourceReference,hierarchy.FolderNodeVersion,hub.Role,hub.AttributeType,activity.AssessmentResourceActivity,hub.Scope,resources.AssessmentResourceVersion,hub.UserGroupAttribute,hierarchy.Node,resources.ResourceVersion', + @User = 'Elfhadmin', + @Retention = '2 DAYS'; END ELSE BEGIN From 011bbcb8053153ac8fd5b84642559eb073a34e22 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Fri, 17 Apr 2026 14:30:13 +0100 Subject: [PATCH 24/24] CT retention days update --- .../Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql index 5dcc36adb..920980036 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7106-Resume-Databricks-Ingestion.sql @@ -9,7 +9,7 @@ BEGIN EXEC dbo.lakeflowSetupChangeTracking @Tables = 'activity.ResourceActivity,resources.VersionStatus,resources.ResourceType,activity.ScormActivity,activity.ActivityStatus,activity.ScormActivityInteraction,activity.ScormActivityInteractionCorrectResponse,resources.VideoResourceVersion,hierarchy.CatalogueNodeVersionProvider,resources.ResourceVersionKeyword,hierarchy.CatalogueNodeVersion,activity.MediaResourceActivity,resources.ResourceVersionAuthor,resources.ResourceVersionEvent,hub.UserProvider,resources.ResourceAccessibility,hierarchy.NodePath,resources.ResourceVersionRating,resources.ResourceVersionEventType,hierarchy.NodeType,hub.User,hierarchy.NodePathNode,hierarchy.VersionStatus,hub.UserProfile,hierarchy.NodeVersion,hierarchy.Publication,hub.Attribute,hierarchy.NodeResource,hierarchy.NodeLink,resources.Resource,hub.RoleUserGroup,hierarchy.CatalogueNodeVersionKeyword,hub.UserGroup,hub.UserUserGroup,resources.WebLinkResourceVersion,resources.ResourceReference,hierarchy.FolderNodeVersion,hub.Role,hub.AttributeType,activity.AssessmentResourceActivity,hub.Scope,resources.AssessmentResourceVersion,hub.UserGroupAttribute,hierarchy.Node,resources.ResourceVersion', @User = 'Elfhadmin', - @Retention = '2 DAYS'; + @Retention = '7 DAYS'; END ELSE BEGIN