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