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..49aef9634 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(); - - vm.MoodleCategories = categories; + var categoriesResult = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); - // 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,32 +735,33 @@ 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."); - } - 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.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); - } + var vm = await this.catalogueService.GetCatalogueAsync(catalogueViewModel.CatalogueNodeVersionId); + var categories = await this.moodleBridgeApiService.GetAllMoodleCategoriesAsync(); + vm.SelectedCategoryId = null; + vm.MoodleCategorySelectList = BuildMoodleCategorySelectList(categories); - vm.MoodleCategorySelectList = new SelectList(selectList, "Value", "Text"); 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); + } } } @@ -764,23 +773,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 +1092,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..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/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..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/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..77fd780ad 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,23 @@ 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); + 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..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; @@ -46,12 +47,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 +83,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; @@ -94,6 +98,7 @@ public MyAccountController( this.multiPageFormService = multiPageFormService; this.cacheService = cacheService; this.configuration = configuration; + this.moodleBridgeApiService = moodleBridgeApiService; } private string LoginWizardCacheKey => $"{this.CurrentUserId}:LoginWizard"; @@ -1494,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; @@ -1508,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/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..87cb49cf6 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleBridgeApiService.cs @@ -0,0 +1,30 @@ +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.Models.User; + 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); + + /// + /// UpdateEmail. + /// + /// The updateEmailaddressViewModel. + /// A representing the result of the asynchronous operation. + Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel); + } +} 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..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/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..6cd57f10c --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/MoodleBridgeApiService.cs @@ -0,0 +1,109 @@ +namespace LearningHub.Nhs.WebUI.Services +{ + 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; + 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; + } + } + + /// + /// 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/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..57dc0839c 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) {
@@ -28,7 +28,7 @@ @foreach (Course item in Model.Courses) {
-

@item.Displayname

+

@item.Displayname

Type: Course
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/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/Home/_MyCoursesAndElearning.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_MyCoursesAndElearning.cshtml index 8923e8e24..dc5880065 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,27 @@ else {
    - @if (Model.MyLearnings.Type == "my-in-progress" && 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.

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

    You have no certificates.

    -

    Certificates when available will be displayed here.

    +

    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.

    } +
    }
    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.

      } -
    + }
    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/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 829bba059..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,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..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 6a727dc60..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.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..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/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/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.Interface/Services/IMoodleBridgeApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs new file mode 100644 index 000000000..3bd55103b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleBridgeApiService.cs @@ -0,0 +1,90 @@ + +using System.Collections.Generic; +using System.Threading.Tasks; +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 +{ + /// + /// IMoodleBridgeApiService. + /// + public interface IMoodleBridgeApiService + { + /// + /// GetUserInstancesByEmailAsync. + /// + /// The current LH User email. + /// A representing the result of the asynchronous operation. + Task GetUserInstancesByEmail(string email); + + /// + /// UpdateEmail. + /// + /// TheupdateEmailaddressViewModel. + /// + Task UpdateEmail(UpdateEmailaddressViewModel updateEmailaddressViewModel); + + /// + /// 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. + /// filterText. + /// 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 36c6d8f5f..64b868475 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 @@ - + @@ -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 5edfe8cc4..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; @@ -229,7 +226,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; } @@ -571,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, @@ -805,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); } @@ -849,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); } @@ -923,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/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..95dd23552 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/DashboardService.cs @@ -8,17 +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; /// /// The DashboardService. @@ -35,6 +33,11 @@ public class DashboardService : IDashboardService /// private readonly IMoodleApiService moodleApiService; + /// + /// The moodleBridgeApiService. + /// + private readonly IMoodleBridgeApiService moodleBridgeApiService; + /// /// The resourceActivityRepository. /// @@ -54,9 +57,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 +70,7 @@ public DashboardService(IMapper mapper, IResourceVersionRepository resourceVersi this.moodleApiService = moodleApiService; this.resourceActivityRepository = resourceActivityRepository; this.resourceRepository = resourceRepository; + this.moodleBridgeApiService = moodleBridgeApiService; } /// @@ -126,28 +131,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 +201,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 +225,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 +240,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 +275,41 @@ 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 + var courses = entrolledCourses?.Results ?? Enumerable.Empty(); + mappedEnrolledCourses = courses + .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, + ResourceUrl = course.CourseUrl, + 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") @@ -345,7 +356,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; @@ -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 + }) ?? Enumerable.Empty(); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); 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/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..71076c3f9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleBridgeApiService.cs @@ -0,0 +1,607 @@ +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; + using LearningHub.Nhs.Models.User; + + /// + /// 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; + } + } + + /// + /// 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; + } + } + + /// + /// 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."); + } + + 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) + { + return null; + } + } + + /// + /// 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."); + } + + 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 + { + var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + + 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 + { + var moodleUserInstanceUserIds = await this.GetUserInstancesByEmail(email); + if (moodleUserInstanceUserIds?.MoodleInstanceUserIds == null || + !moodleUserInstanceUserIds.MoodleInstanceUserIds.Any()) + { + throw new ArgumentException("UserIds are required."); + } + 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."); + } + 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; + } + } + + /// + /// 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..8d1793650 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,47 @@ 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, + ResourceUrl = course.CourseUrl, + 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()?? new List(); ; } // 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 +281,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 +351,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.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, + ResourceUrl = course.CourseUrl, + 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()?? new List(); } } // 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 +719,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 +746,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, + }) ?? Enumerable.Empty(); } var allCertificates = resourceCertificates.Concat(mappedCourseCertificates); @@ -764,8 +781,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 +792,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/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/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 73c887df7..56b9450ef 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; @@ -34,11 +33,11 @@ public static void AddServices(this IServiceCollection services, IConfiguration } else { - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } services.AddHttpClient(); + services.AddHttpClient(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -90,17 +89,8 @@ public static void AddServices(this IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); 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/LearningHub.Nhs.OpenApi.Tests.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/LearningHub.Nhs.OpenApi.Tests.csproj index 93461ce2b..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.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() 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[] 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..5f8b67774 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleBridgeController.cs @@ -0,0 +1,66 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using System.Threading.Tasks; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using LearningHub.Nhs.Models.User; + + /// + /// 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); + } + + /// + /// 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. + /// + /// 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..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/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..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 aebb8bb75..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 acfb75739..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 9d86c24a0..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 f31ff35e5..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/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/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/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index 3a7660599..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/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/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/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.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.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index e1b7896b8..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 4acbd2e13..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.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index a6a0db7ad..5b4022af9 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,10 @@ + + + + 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..2b2bcda6e 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 +:r .\Scripts\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..920980036 --- /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,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 = '7 DAYS'; +END +ELSE +BEGIN + PRINT 'Change Tracking is NOT enabled on this database. Skipping execution.'; +END \ No newline at end of file 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..09e405b1d --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7116-mib_new_env.sql @@ -0,0 +1,8 @@ + +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 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 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.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 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..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 b7cca29dc..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/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.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index 6ee630341..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/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.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index dbb1f6a30..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/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/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index 98cec165a..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/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..993448a3a 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; } /// @@ -516,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); } @@ -536,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); } @@ -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()); } /// @@ -647,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); } @@ -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(true); } /// @@ -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(true); } private string EncodeSearchText(string searchText) 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..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 b275eae94..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 a18eb4196..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 bb23ca661..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 82a9b4aa6..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 de745363e..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