diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml
index 81156cd91..50c8df8f4 100644
--- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml
+++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml
@@ -1,5 +1,5 @@
@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel
-@inject Microsoft.Extensions.Configuration.IConfiguration Configuration;
+@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService;
@using System.Linq;
@using System.Web;
@@ -28,19 +28,30 @@
&query={searchSignalQueryEncoded}&title={payload?.DocumentFields?.Title}";
}
- string GetMoodleCourseUrl(string courseId)
+ string GetMoodleCourseUrl(string courseIdWithPrefix)
{
- var prefix = "M";
- if (courseId.StartsWith(prefix))
+ const string prefix = "M";
+
+ if (string.IsNullOrWhiteSpace(courseIdWithPrefix))
+ {
+ return string.Empty;
+ }
+
+ if (!courseIdWithPrefix.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
- courseId = courseId.Replace(prefix, ""); ;
+ return string.Empty;
}
- var apiBaseUrl = Configuration["MoodleAPIConfig:BaseUrl"];
- string path = $"course/view.php";
- string returnUrl = $@"{apiBaseUrl}{path}?id={courseId}";
+ var courseIdPart = courseIdWithPrefix.Substring(prefix.Length);
- return returnUrl;
+ if (int.TryParse(courseIdPart, out int courseId))
+ {
+ return moodleApiService.GetCourseUrl(courseId);
+ }
+ else
+ {
+ return string.Empty;
+ }
}
bool showCatalogueFieldsInResources = ViewBag.ShowCatalogueFieldsInResources == null || ViewBag.ShowCatalogueFieldsInResources == true;
@@ -93,7 +104,7 @@
Type:
- @UtilityHelper.GetPrettifiedResourceTypeNameMoodle(UtilityHelper.ToEnum(item.ResourceType), 0)
+ @UtilityHelper.GetPrettifiedResourceTypeName(UtilityHelper.ToEnum(item.ResourceType), 0)
@if (item.ResourceType != "moodle")
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs
index de178b6a2..20e9b1904 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs
@@ -14,16 +14,26 @@ public class MoodleConfig
///
/// Gets or sets the base url for the Moodle service.
///
- public string APIBaseUrl { get; set; } = null!;
+ public string ApiBaseUrl { get; set; } = null!;
///
/// Gets or sets the Web service Rest Format.
///
- public string APIWSRestFormat { get; set; } = null!;
+ public string ApiWsRestFormat { get; set; } = null!;
///
/// Gets or sets the token.
///
- public string APIWSToken { get; set; } = null!;
+ public string ApiWsToken { get; set; } = null!;
+
+ ///
+ /// Gets or sets the token.
+ ///
+ public string ApiPath { get; set; } = "webservice/rest/server.php";
+
+ ///
+ /// Gets or sets the token.
+ ///
+ public string CoursePath { get; set; } = "course/view.php";
}
}
diff --git a/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 aa019e18c..6fe3b2dfe 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/IMoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs
index e366a4370..ee573d43b 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs
@@ -1,7 +1,5 @@
-using System;
+using LearningHub.Nhs.Models.Moodle.API;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
namespace LearningHub.Nhs.OpenApi.Services.Interface.Services
@@ -17,5 +15,22 @@ public interface IMoodleApiService
/// The current LH User Id.
/// A representing the result of the asynchronous operation.
Task GetMoodleUserIdByUsernameAsync(int currentUserId);
+
+ ///
+ /// GetEnrolledCoursesAsync.
+ ///
+ /// Moodle user id.
+ /// pageNumber.
+ /// List of MoodleCourseResponseModel.
+ Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber);
+
+ ///
+ /// GetEnrolledCoursesAsync.
+ ///
+ /// Moodle user id.
+ /// Moodle course id.
+ /// pageNumber.
+ /// List of MoodleCourseResponseModel.
+ Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber);
}
}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs
index 73af93977..2c9fc410a 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs
@@ -20,9 +20,9 @@ public class MoodleHttpClient : IMoodleHttpClient, IDisposable
private readonly HttpClient httpClient = new();
private bool initialised = false;
- private string moodleAPIBaseUrl;
- private string moodleAPIMoodleWSRestFormat;
- private string moodleAPIWSToken;
+ private string moodleApiUrl;
+ private string moodleApiMoodleWsRestFormat;
+ private string moodleApiWsToken;
///
/// Initializes a new instance of the class.
@@ -34,9 +34,9 @@ public MoodleHttpClient(HttpClient httpClient, IOptions moodleConf
this.moodleConfig = moodleConfig.Value;
this.httpClient = httpClient;
- this.moodleAPIBaseUrl = this.moodleConfig.APIBaseUrl + "webservice/rest/server.php";
- this.moodleAPIMoodleWSRestFormat = this.moodleConfig.APIWSRestFormat;
- this.moodleAPIWSToken = this.moodleConfig.APIWSToken;
+ this.moodleApiUrl = this.moodleConfig.ApiBaseUrl + this.moodleConfig.ApiPath;
+ this.moodleApiMoodleWsRestFormat = this.moodleConfig.ApiWsRestFormat;
+ this.moodleApiWsToken = this.moodleConfig.ApiWsToken;
}
///
@@ -45,7 +45,7 @@ public MoodleHttpClient(HttpClient httpClient, IOptions moodleConf
/// The .
public async Task GetClient()
{
- this.Initialise(this.moodleAPIBaseUrl);
+ this.Initialise(this.moodleApiUrl);
return this.httpClient;
}
@@ -55,8 +55,8 @@ public async Task GetClient()
/// defaultParameters.
public string GetDefaultParameters()
{
- string defaultParameters = $"wstoken={this.moodleAPIWSToken}"
- + $"&moodlewsrestformat={this.moodleAPIMoodleWSRestFormat}";
+ string defaultParameters = $"wstoken={this.moodleApiWsToken}"
+ + $"&moodlewsrestformat={this.moodleApiMoodleWsRestFormat}";
return defaultParameters;
}
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 9002a0237..bf7b23c23 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs
index 90ffc4280..08786db8d 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs
+++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs
@@ -11,6 +11,7 @@
using System.Net;
using System.Net.Http;
using System.Text;
+ using System.Text.Json;
using System.Threading.Tasks;
///
@@ -31,6 +32,7 @@ public MoodleApiService(IMoodleHttpClient moodleHttpClient, ILogger
/// GetMoodleUserIdByUsernameAsync.
///
@@ -51,16 +53,89 @@ public async Task GetMoodleUserIdByUsernameAsync(int currentUserId)
}
+ ///
+ /// GetEnrolledCoursesAsync.
+ ///
+ /// Moodle user id.
+ /// The page Number.
+ /// A representing the result of the asynchronous operation.
+ public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber)
+ {
+ var parameters = new Dictionary
+ {
+ { "userid", userId.ToString() }
+ };
+
+ // Fetch enrolled courses
+ var enrolledCourses = await GetCallMoodleApiAsync>(
+ "core_enrol_get_users_courses",
+ parameters
+ );
+
+ if (enrolledCourses == null || enrolledCourses.Count == 0)
+ return new List();
+
+ // Load course completion info in parallel
+ var completionTasks = enrolledCourses
+ .Where(c => c.Id.HasValue)
+ .Select(async course =>
+ {
+ try
+ {
+ course.CourseCompletionViewModel = await GetCourseCompletionAsync(userId, course.Id.Value, pageNumber);
+ }
+ catch (Exception ex)
+ {
+ course.CourseCompletionViewModel = new MoodleCourseCompletionModel
+ {
+ CompletionStatus = null,
+ };
+ }
+
+ return course;
+ });
+
+ var enrichedCourses = await Task.WhenAll(completionTasks);
+
+ return enrichedCourses.ToList();
+
+ }
+
+ ///
+ /// GetEnrolledCoursesAsync.
+ ///
+ /// Moodle user id.
+ /// Moodle course id.
+ /// pageNumber.
+ /// List of MoodleCourseResponseModel.
+ public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber)
+ {
+ var parameters = new Dictionary
+ {
+ { "userid", userId.ToString() },
+ { "courseid", courseId.ToString() }
+ };
+
+ // Call Moodle API and parse response
+ var result = await GetCallMoodleApiAsync("core_completion_get_course_completion_status", parameters);
+
+ // If Moodle did not return an exception, return parsed completion data
+ if (result.Warnings.Count == 0)
+ {
+ // Optionally map/convert if needed
+ return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result));
+ }
+
+ return new MoodleCourseCompletionModel(); // Return empty model or null as fallback
+ }
+
private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary parameters)
{
var client = await this.moodleHttpClient.GetClient();
string defaultParameters = this.moodleHttpClient.GetDefaultParameters();
- // Build URL query
- var queryBuilder = new StringBuilder();
-
- queryBuilder.Append($"&wsfunction={wsFunction}");
-
+ // Build URL query string
+ var queryBuilder = new StringBuilder($"&wsfunction={wsFunction}");
foreach (var param in parameters)
{
queryBuilder.Append($"&{param.Key}={Uri.EscapeDataString(param.Value)}");
@@ -73,7 +148,33 @@ private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary(result);
+ // Moodle may still return an error with 200 OK
+ try
+ {
+ using var document = JsonDocument.Parse(result);
+ var root = document.RootElement;
+
+ if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("exception", out var exceptionProp))
+ {
+ string? message = root.TryGetProperty("message", out var messageProp)
+ ? messageProp.GetString()
+ : "Unknown error";
+
+ this.logger.LogError($"Moodle returned an exception: {exceptionProp.GetString()}, Message: {message}");
+ throw new Exception($"Moodle API Error: {exceptionProp.GetString()}, Message: {message}");
+ }
+ }
+ catch (System.Text.Json.JsonException ex)
+ {
+ this.logger.LogError(ex, "Failed to parse Moodle API response as JSON.");
+ throw;
+ }
+
+ var deserialized = JsonConvert.DeserializeObject(result);
+
+ return deserialized == null
+ ? throw new Exception($"Failed to deserialize Moodle API response into type {typeof(T).Name}. Raw response: {result}")
+ : deserialized;
}
else if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
{
@@ -82,9 +183,10 @@ private async Task GetCallMoodleApiAsync(string wsFunction, Dictionary
@@ -43,5 +45,26 @@ public async Task GetMoodleUserId(int? currentUserId)
return this.Ok(0);
}
}
+
+ ///
+ /// GetEnrolledCoursesAsync.
+ ///
+ /// Moodle user id.
+ /// The page Number.
+ /// A representing the result of the asynchronous operation.
+ [HttpGet]
+ [Route("GetEnrolledCourses/{currentUserId?}")]
+ public async Task GetEnrolledCoursesAsync(int? currentUserId, int pageNumber = 0)
+ {
+ if (currentUserId.HasValue)
+ {
+ var entrolledCourses = await this.moodleService.GetEnrolledCoursesAsync(currentUserId.Value, pageNumber);
+ return this.Ok(entrolledCourses);
+ }
+ else
+ {
+ return this.Ok(0);
+ }
+ }
}
}
diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
index 61417224f..181c8eaf8 100644
--- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
+++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json
@@ -121,8 +121,8 @@
"AuthTimeout": 20
},
"Moodle": {
- "APIBaseUrl": "",
- "APIWSRestFormat": "json",
- "APIWSToken": ""
+ "ApiBaseUrl": "",
+ "ApiWsRestFormat": "json",
+ "ApiWsToken": ""
}
}