diff --git a/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs b/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs new file mode 100644 index 000000000..ec874477e --- /dev/null +++ b/LearningHub.Nhs.WebUI/Configuration/MoodleApiConfig.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.WebUI.Configuration +{ + /// + /// The Moodle Settings. + /// + public class MoodleApiConfig + { + /// + /// Gets or sets the base url for the Moodle service. + /// + public string BaseUrl { get; set; } = null!; + + /// + /// Gets or sets the Web service Rest Format. + /// + public string MoodleWSRestFormat { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string WSToken { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + public string ApiPath { get; set; } = "webservice/rest/server.php"; + + /// + /// Gets or sets the token. + /// + public string CoursePath { get; set; } = "course/view.php"; + } +} diff --git a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs index fc3b7f76a..961e68cdd 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs @@ -28,6 +28,7 @@ public class ContributeController : BaseController private readonly IFileService fileService; private readonly IResourceService resourceService; private readonly IUserService userService; + private readonly IUserGroupService userGroupService; /// /// Initializes a new instance of the class. @@ -37,6 +38,7 @@ public class ContributeController : BaseController /// Logger. /// Settings. /// User service. + /// userGroupService. /// File service. /// Resource service. /// Azure media service. @@ -48,6 +50,7 @@ public ContributeController( ILogger logger, IOptions settings, IUserService userService, + IUserGroupService userGroupService, IFileService fileService, IResourceService resourceService, IAzureMediaService azureMediaService, @@ -58,6 +61,7 @@ public ContributeController( this.authConfig = authConfig; this.userService = userService; + this.userGroupService = userGroupService; this.fileService = fileService; this.resourceService = resourceService; this.azureMediaService = azureMediaService; @@ -167,7 +171,8 @@ public async Task CreateVersion(int resourceId) [Route("my-contributions/{selectedTab}/{catalogueId}/{nodeId}")] public async Task MyContributions() { - if ((this.User.IsInRole("ReadOnly") || this.User.IsInRole("BasicUser")) && !await this.resourceService.UserHasPublishedResourcesAsync()) + bool catalogueContributionPermission = await this.userGroupService.UserHasCatalogueContributionPermission(); + if ((this.User.IsInRole("ReadOnly") || this.User.IsInRole("BasicUser")) || (!catalogueContributionPermission && (!await this.resourceService.UserHasPublishedResourcesAsync()))) { return this.RedirectToAction("AccessDenied", "Home"); } @@ -197,8 +202,9 @@ public async Task UploadArticleFile(IFormCollection inputForm) var file = inputForm.Files[0]; int.TryParse(inputForm["resourceVersionId"], out int resourceVersionId); int.TryParse(inputForm["changeingFileId"], out int existingFileId); + int.TryParse(inputForm["fileSize"], out int fileSize); var currentUserId = this.User.Identity.GetCurrentUserId(); - return await this.contributeService.ProcessArticleFileAsync(resourceVersionId, file, existingFileId, currentUserId); + return await this.contributeService.ProcessArticleFileAsync(resourceVersionId, file, fileSize, existingFileId, currentUserId); } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index d5f53c00d..57c170ae6 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -11,6 +11,7 @@ namespace LearningHub.Nhs.WebUI.Controllers using LearningHub.Nhs.Models.Content; using LearningHub.Nhs.Models.Enums.Content; using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; @@ -218,13 +219,12 @@ public async Task Index(string myLearningDashboard = "my-in-progr var cataloguesTask = this.dashboardService.GetCataloguesAsync(catalogueDashboard, 1); var userGroupsTask = this.userGroupService.UserHasCatalogueContributionPermission(); - var enrolledCoursesTask = Task.FromResult(new List()); - var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; - this.ViewBag.EnableMoodle = enableMoodle; - this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0; + var enrolledCoursesTask = Task.FromResult(new List()); + (bool enableMoodle, int currentMoodleUserId) = await this.GetMoodleFeatureStateAsync(); + if (enableMoodle && myLearningDashboard == "my-enrolled-courses") { - enrolledCoursesTask = this.dashboardService.GetEnrolledCoursesFromMoodleAsync(this.CurrentMoodleUserId, 1); + enrolledCoursesTask = this.dashboardService.GetEnrolledCoursesFromMoodleAsync(currentMoodleUserId, 1); } await Task.WhenAll(learningTask, resourcesTask, cataloguesTask, userGroupsTask); @@ -280,9 +280,7 @@ public async Task LoadPage(string dashBoardTray = "my-learning", Catalogues = new Nhs.Models.Dashboard.DashboardCatalogueResponseViewModel { Type = catalogueDashBoard }, }; - var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; - this.ViewBag.EnableMoodle = enableMoodle; - this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0; + (bool enableMoodle, int currentMoodleUserId) = await this.GetMoodleFeatureStateAsync(); bool isAjax = this.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; @@ -449,5 +447,28 @@ private async Task GetLandingPageContent(bool preview = fa return new LandingPageViewModel { PageSectionDetailViewModels = new List(), PageViewModel = new PageViewModel { PageSections = new List { } } }; } } + + /// + /// Asynchronously retrieves the state of the Moodle feature and the current Moodle user ID. + /// + /// The method checks if the Moodle feature is enabled and retrieves the current Moodle + /// user ID. If the user ID is not already set, it attempts to obtain it asynchronously from the dashboard + /// service. + /// A tuple containing a boolean indicating whether the Moodle feature is enabled and an integer representing + /// the current Moodle user ID. + private async Task<(bool enableMoodle, int currentMoodleUserId)> GetMoodleFeatureStateAsync() + { + var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result; + this.ViewBag.EnableMoodle = enableMoodle; + int currentMoodleUserId = this.CurrentMoodleUserId; + + if (currentMoodleUserId == 0) + { + currentMoodleUserId = await this.dashboardService.GetMoodleUserIdAsync(this.CurrentUserId); + } + + this.ViewBag.ValidMoodleUser = currentMoodleUserId > 0; + return (enableMoodle, currentMoodleUserId); + } } } diff --git a/LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs b/LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs deleted file mode 100644 index e0601d156..000000000 --- a/LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Helpers; - -/// -/// Represents the types of resources available in Moodle. -/// -public enum ResourceTypeEnumMoodle -{ - /// - /// The undefined resource type. - /// - Undefined, - - /// - /// The article resource type. - /// - Article, - - /// - /// The audio resource type. - /// - Audio, - - /// - /// The embedded resource type. - /// - Embedded, - - /// - /// The equipment resource type. - /// - Equipment, - - /// - /// The image resource type. - /// - Image, - - /// - /// The SCORM resource type. - /// - Scorm, - - /// - /// The video resource type. - /// - Video, - - /// - /// The web link resource type. - /// - WebLink, - - /// - /// The generic file resource type. - /// - GenericFile, - - /// - /// The clinical case resource type. - /// - Case, - - /// - /// The assessment resource type. - /// - Assessment, - - /// - /// The HTML resource type. - /// - Html, - - /// - /// The Moodle resource type. - /// - Moodle, - - /// - /// The Moodle course resource type. - /// - Course, -} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index ef71191f9..e1732d5a3 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -30,25 +30,7 @@ public static class UtilityHelper { "genericfile", ResourceTypeEnum.GenericFile }, { "image", ResourceTypeEnum.Image }, { "html", ResourceTypeEnum.Html }, - }; - - /// TODO: Remove this method after adding to Moodle resource types to models project. - /// - /// Findwise Moodle resource type dictionary. - /// - public static readonly Dictionary FindwiseResourceMoodleTypeDict = new Dictionary() - { - { "video", ResourceTypeEnumMoodle.Video }, - { "article", ResourceTypeEnumMoodle.Article }, - { "case", ResourceTypeEnumMoodle.Case }, - { "weblink", ResourceTypeEnumMoodle.WebLink }, - { "audio", ResourceTypeEnumMoodle.Audio }, - { "scorm", ResourceTypeEnumMoodle.Scorm }, - { "assessment", ResourceTypeEnumMoodle.Assessment }, - { "genericfile", ResourceTypeEnumMoodle.GenericFile }, - { "image", ResourceTypeEnumMoodle.Image }, - { "html", ResourceTypeEnumMoodle.Html }, - { "moodle", ResourceTypeEnumMoodle.Course }, + { "moodle", ResourceTypeEnum.Moodle }, }; /// @@ -166,54 +148,6 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType } } - /// TODO: Remove this method after adding to Moodle resource types to models project. - /// - /// Returns a prettified resource type name, suitable for display in the UI. Includes video/audio duration string. - /// - /// The resource type. - /// The media duration in milliseconds. - /// The resource type name, and duration if applicable. - public static string GetPrettifiedResourceTypeNameMoodle(ResourceTypeEnumMoodle resourceType, int? durationInMilliseconds = 0) - { - switch (resourceType) - { - case ResourceTypeEnumMoodle.Assessment: - return "Assessment"; - case ResourceTypeEnumMoodle.Article: - return "Article"; - case ResourceTypeEnumMoodle.Audio: - string durationText = GetDurationText(durationInMilliseconds ?? 0); - durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; - return "Audio" + durationText; - case ResourceTypeEnumMoodle.Equipment: - return "Equipment"; - case ResourceTypeEnumMoodle.Image: - return "Image"; - case ResourceTypeEnumMoodle.Scorm: - return "elearning"; - case ResourceTypeEnumMoodle.Video: - durationText = GetDurationText(durationInMilliseconds ?? 0); - durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; - return "Video" + durationText; - case ResourceTypeEnumMoodle.WebLink: - return "Web link"; - case ResourceTypeEnumMoodle.GenericFile: - return "File"; - case ResourceTypeEnumMoodle.Embedded: - return "Embedded"; - case ResourceTypeEnumMoodle.Case: - return "Case"; - case ResourceTypeEnumMoodle.Html: - return "HTML"; - case ResourceTypeEnumMoodle.Moodle: - return "Course"; - case ResourceTypeEnumMoodle.Course: - return "Course"; - default: - return "File"; - } - } - /// /// Returns a prettified resource type name, suitable for display in the UI. Excludes video/audio duration string. /// @@ -247,6 +181,8 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType return "Case"; case ResourceTypeEnum.Html: return "HTML"; + case ResourceTypeEnum.Moodle: + return "Course"; default: return "File"; } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs b/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs index 1f7f4154e..2c982adf1 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs @@ -87,10 +87,11 @@ public interface IContributeService /// /// Resource version id. /// File. + /// fileSize. /// Changing file id. /// Current user id. /// A representing the result of the asynchronous operation. - Task ProcessArticleFileAsync(int resourceVersionId, IFormFile file, int changeingFileId, int currentUserId); + Task ProcessArticleFileAsync(int resourceVersionId, IFormFile file, int fileSize,int changeingFileId, int currentUserId); /// /// The ProcessResourceAttachedFileAsync. diff --git a/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs b/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs index 9eb8c3266..70a39d2fb 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.WebUI.Models; /// @@ -47,6 +48,13 @@ public interface IDashboardService /// The current User Id type. /// The page Number. /// A representing the result of the asynchronous operation. - Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber); + Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber); + + /// + /// GetEnrolledCoursesFromMoodleAsync. + /// + /// The current User Id type. + /// A representing the result of the asynchronous operation. + Task GetMoodleUserIdAsync(int currentUserId); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs index d92c01fad..5796c3482 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs @@ -2,29 +2,34 @@ { using System.Collections.Generic; using System.Threading.Tasks; - using LearningHub.Nhs.Models.Dashboard; - using LearningHub.Nhs.WebUI.Models; + using LearningHub.Nhs.Models.Moodle.API; + using MoodleCourseCompletionModel = LearningHub.Nhs.Models.Moodle.API.MoodleCourseCompletionModel; /// /// IMoodleApiService. /// public interface IMoodleApiService { + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// The current LH User Id. + /// A representing the result of the asynchronous operation. + Task GetMoodleUserIdByUsernameAsync(int currentUserId); + /// /// GetEnrolledCoursesAsync. /// /// Moodle user id. /// pageNumber. - /// List of MoodleCourseResponseViewModel. - Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); + /// List of MoodleCourseResponseModel. + Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber); /// - /// GetEnrolledCoursesAsync. + /// GetCourseUrl. /// - /// Moodle user id. - /// Moodle course id. - /// pageNumber. - /// List of MoodleCourseResponseViewModel. - Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber); + /// course Id. + /// return course URL. + string GetCourseUrl(int courseId); } } diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index dc2cf661d..b69ead582 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/DashboardViewModel.cs b/LearningHub.Nhs.WebUI/Models/DashboardViewModel.cs index 9ac98c7e0..d7ddeda0c 100644 --- a/LearningHub.Nhs.WebUI/Models/DashboardViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/DashboardViewModel.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Moodle.API; /// /// Defines the . @@ -33,6 +34,6 @@ public DashboardViewModel() /// /// Gets or sets a list of enrolled courses to be displayed in the dashboard. /// - public List EnrolledCourses { get; set; } + public List EnrolledCourses { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Models/MoodleCompletionResponseViewModel.cs b/LearningHub.Nhs.WebUI/Models/MoodleCompletionResponseViewModel.cs deleted file mode 100644 index 1692a32e6..000000000 --- a/LearningHub.Nhs.WebUI/Models/MoodleCompletionResponseViewModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models -{ - /// - /// MoodleCompletionResponseViewModel. - /// - public class MoodleCompletionResponseViewModel - { - /// - /// Gets or sets the completion status. - /// - public string Exception { get; set; } - - /// - /// Gets or sets error code. - /// - public string Errorcode { get; set; } - - /// - /// Gets or sets Error message. - /// - public string Message { get; set; } - - /// - /// Gets or sets Debug info. - /// - public string Debuginfo { get; set; } - } -} diff --git a/LearningHub.Nhs.WebUI/Models/MoodleCourseCompletionViewModel.cs b/LearningHub.Nhs.WebUI/Models/MoodleCourseCompletionViewModel.cs deleted file mode 100644 index 06eb5cb8f..000000000 --- a/LearningHub.Nhs.WebUI/Models/MoodleCourseCompletionViewModel.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models -{ - using System.Collections.Generic; - - /// - /// MoodleCourseCompletionViewModel. - /// - public class MoodleCourseCompletionViewModel - { - /// - /// Gets or sets the completion status. - /// - public CompletStatus CompletionStatus { get; set; } - - /// - /// Gets or sets the list of warnings. - /// - public List Warnings { get; set; } - - /// - /// CompletionStatus. - /// - public class CompletStatus - { - /// - /// Gets or sets a value indicating whether the course is completed. - /// - public bool Completed { get; set; } - - /// - /// Gets or sets the aggregation method. - /// - public int Aggregation { get; set; } - - /// - /// Gets or sets the list of completions. - /// - public List Completions { get; set; } - - /// - /// Completion. - /// - public class Completion - { - /// - /// Gets or sets the type of completion. - /// - public int Type { get; set; } - - /// - /// Gets or sets the title of the completion requirement. - /// - public string Title { get; set; } - - /// - /// Gets or sets the status of the completion. - /// - public string Status { get; set; } - - /// - /// Gets or sets a value indicating whether the requirement is complete. - /// - public bool Complete { get; set; } - - /// - /// Gets or sets the timestamp when completion was achieved. - /// - public long? TimeCompleted { get; set; } - - /// - /// Gets or sets the completion details. - /// - public CompletionDetails Details { get; set; } - - /// - /// CompletionDetails. - /// - public class CompletionDetails - { - /// - /// Gets or sets the type of completion requirement. - /// - public string Type { get; set; } - - /// - /// Gets or sets the criteria for completion. - /// - public string Criteria { get; set; } - - /// - /// Gets or sets the requirement for completion. - /// - public string Requirement { get; set; } - - /// - /// Gets or sets the status of the requirement. - /// - public string Status { get; set; } - } - } - } - } -} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Models/MoodleCourseResponseViewModel.cs b/LearningHub.Nhs.WebUI/Models/MoodleCourseResponseViewModel.cs deleted file mode 100644 index b214cf072..000000000 --- a/LearningHub.Nhs.WebUI/Models/MoodleCourseResponseViewModel.cs +++ /dev/null @@ -1,165 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models -{ - using System.Collections.Generic; - - /// - /// MoodleCourseResponseViewModel. - /// - public class MoodleCourseResponseViewModel - { - /// - /// Gets or sets the ID. - /// - public int? Id { get; set; } - - /// - /// Gets or sets the short name. - /// - public string ShortName { get; set; } - - /// - /// Gets or sets the full name. - /// - public string FullName { get; set; } - - /// - /// Gets or sets the display name. - /// - public string DisplayName { get; set; } - - /// - /// Gets or sets the enrolled user count. - /// - public int? EnrolledUserCount { get; set; } - - /// - /// Gets or sets the ID number. - /// - public string IdNumber { get; set; } - - /// - /// Gets or sets the visibility status. - /// - public int? Visible { get; set; } - - /// - /// Gets or sets the summary. - /// - public string Summary { get; set; } - - /// - /// Gets or sets the summary format. - /// - public int? SummaryFormat { get; set; } - - /// - /// Gets or sets the format. - /// - public string Format { get; set; } - - /// - /// Gets or sets the course image URL. - /// - public string CourseImage { get; set; } - - /// - /// Gets or sets a value indicating whether grades are shown. - /// - public bool? ShowGrades { get; set; } - - /// - /// Gets or sets the language. - /// - public string Lang { get; set; } - - /// - /// Gets or sets a value indicating whether completion is enabled. - /// - public bool? EnableCompletion { get; set; } - - /// - /// Gets or sets a value indicating whether completion has criteria. - /// - public bool? CompletionHasCriteria { get; set; } - - /// - /// Gets or sets a value indicating whether completion is user-tracked. - /// - public bool? CompletionUserTracked { get; set; } - - /// - /// Gets or sets the category ID. - /// - public int? Category { get; set; } - - /// - /// Gets the progress percentage formatted as a string. - /// - public string ProgressPercentage => $"{System.Math.Round(this.Progress ?? 0)}%"; - - /// - /// Gets or sets the progress. - /// - public double? Progress { get; set; } - - /// - /// Gets or sets the completion status. - /// - public bool? Completed { get; set; } - - /// - /// Gets or sets the start date (Unix timestamp). - /// - public long? StartDate { get; set; } - - /// - /// Gets or sets the end date. - /// - public int? EndDate { get; set; } - - /// - /// Gets or sets the marker. - /// - public int? Marker { get; set; } - - /// - /// Gets or sets the last access timestamp. - /// - public int? LastAccess { get; set; } - - /// - /// Gets or sets a value indicating whether the course is a favorite. - /// - public bool? IsFavourite { get; set; } - - /// - /// Gets or sets a value indicating whether the course is hidden. - /// - public bool? Hidden { get; set; } - - /// - /// Gets or sets the list of overview files. - /// - public List OverviewFiles { get; set; } - - /// - /// Gets or sets a value indicating whether activity dates are shown. - /// - public bool? ShowActivityDates { get; set; } - - /// - /// Gets or sets a value indicating whether completion conditions are shown. - /// - public bool? ShowCompletionConditions { get; set; } - - /// - /// Gets or sets the last modified timestamp (Unix timestamp). - /// - public long? TimeModified { get; set; } - - /// - /// Gets or sets the moodle course completion view model. - /// - public MoodleCourseCompletionViewModel CourseCompletionViewModel { get; set; } - } -} diff --git a/LearningHub.Nhs.WebUI/Models/MoodleOverviewFileViewModel.cs b/LearningHub.Nhs.WebUI/Models/MoodleOverviewFileViewModel.cs deleted file mode 100644 index 3dd335c41..000000000 --- a/LearningHub.Nhs.WebUI/Models/MoodleOverviewFileViewModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models -{ - /// - /// MoodleOverviewFileViewModel. - /// - public class MoodleOverviewFileViewModel - { - /// - /// Gets or sets the file name. - /// - public string? FileName { get; set; } - - /// - /// Gets or sets the file path. - /// - public string? FilePath { get; set; } - - /// - /// Gets or sets the file size in bytes. - /// - public int FileSize { get; set; } - - /// - /// Gets or sets the file URL. - /// - public string? FileUrl { get; set; } - - /// - /// Gets or sets the time the file was modified (Unix timestamp). - /// - public long TimeModified { get; set; } - - /// - /// Gets or sets the MIME type of the file. - /// - public string? MimeType { get; set; } - } -} diff --git a/LearningHub.Nhs.WebUI/Models/MoodleUserResponseViewModel.cs b/LearningHub.Nhs.WebUI/Models/MoodleUserResponseViewModel.cs deleted file mode 100644 index 7940dc725..000000000 --- a/LearningHub.Nhs.WebUI/Models/MoodleUserResponseViewModel.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models -{ - using System.Collections.Generic; - - /// - /// MoodleUserResponseViewModel. - /// - public class MoodleUserResponseViewModel - { - /// - /// Gets or sets the list of users. - /// - public List Users { get; set; } - - /// - /// Gets or sets the warnings. - /// - public List Warnings { get; set; } - - /// - /// MoodleUser. - /// - public class MoodleUser - { - /// - /// Gets or sets the user ID. - /// - public int Id { get; set; } - - /// - /// Gets or sets the username. - /// - public string Username { get; set; } - - /// - /// Gets or sets the first name. - /// - public string FirstName { get; set; } - - /// - /// Gets or sets the last name. - /// - public string LastName { get; set; } - - /// - /// Gets or sets the full name. - /// - public string FullName { get; set; } - - /// - /// Gets or sets the email. - /// - public string Email { get; set; } - - /// - /// Gets or sets the department. - /// - public string Department { get; set; } - - /// - /// Gets or sets the first access timestamp. - /// - public long FirstAccess { get; set; } - - /// - /// Gets or sets the last access timestamp. - /// - public long LastAccess { get; set; } - - /// - /// Gets or sets the authentication method. - /// - public string Auth { get; set; } - - /// - /// Gets or sets a value indicating whether the user is suspended. - /// - public bool Suspended { get; set; } - - /// - /// Gets or sets a value indicating whether the user is confirmed. - /// - public bool Confirmed { get; set; } - - /// - /// Gets or sets the language. - /// - public string Lang { get; set; } - - /// - /// Gets or sets the theme. - /// - public string Theme { get; set; } - - /// - /// Gets or sets the timezone. - /// - public string Timezone { get; set; } - - /// - /// Gets or sets the mail format. - /// - public int MailFormat { get; set; } - - /// - /// Gets or sets the forum tracking preference. - /// - public int TrackForums { get; set; } - - /// - /// Gets or sets the small profile image URL. - /// - public string ProfileImageUrlSmall { get; set; } - - /// - /// Gets or sets the profile image URL. - /// - public string ProfileImageUrl { get; set; } - } - } -} diff --git a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs index 5ed5cc926..eb2c2fa2c 100644 --- a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs @@ -78,6 +78,9 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + var moodleApiSettings = configuration.GetSection("MoodleAPIConfig"); + services.Configure(moodleApiSettings); + var settingsSection = configuration.GetSection("Settings"); services.Configure(settingsSection); diff --git a/LearningHub.Nhs.WebUI/Services/ContributeService.cs b/LearningHub.Nhs.WebUI/Services/ContributeService.cs index 68a2b4cf6..a78d3eb7d 100644 --- a/LearningHub.Nhs.WebUI/Services/ContributeService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContributeService.cs @@ -382,10 +382,11 @@ public async Task GetFileChunkDetail(int fileChunkDeta /// /// The resourceVersionId. /// The file. + /// The fileSize. /// The existingFileId. /// The currentUserId. /// The . - public async Task ProcessArticleFileAsync(int resourceVersionId, IFormFile file, int existingFileId, int currentUserId) + public async Task ProcessArticleFileAsync(int resourceVersionId, IFormFile file, int fileSize, int existingFileId, int currentUserId) { var filelocation = string.Empty; string extension = Path.GetExtension(file.FileName).Replace(".", string.Empty); @@ -415,7 +416,7 @@ public async Task ProcessArticleFileAsync(int resourceVersionI FileTypeId = fileType == null ? 0 : fileType.Id, FileName = file.FileName, FilePath = filelocation, - FileSize = (int)(file.Length / 1000), + FileSize = (int)(fileSize / 1000), ReplacedFileId = existingFileId, }); @@ -425,7 +426,7 @@ public async Task ProcessArticleFileAsync(int resourceVersionI ResourceVersionId = resourceVersionId, FileName = file.FileName, FileTypeId = fileType == null ? 0 : fileType.Id, - FileSizeKb = (int)(file.Length / 1000), + FileSizeKb = (int)(fileSize / 1000), Invalid = false, }; } diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 607d4f2be..bde127b81 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -2,14 +2,11 @@ { using System; using System.Collections.Generic; - using System.Net.Http; - using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Entities.Analytics; - using LearningHub.Nhs.Models.Entities.Reporting; - using LearningHub.Nhs.Services.Interface; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; @@ -20,7 +17,7 @@ /// public class DashboardService : BaseService, IDashboardService { - private readonly IMoodleHttpClient moodleHttpClient; + private readonly IMoodleApiService moodleApiService; /// /// Initializes a new instance of the class. @@ -28,11 +25,11 @@ public class DashboardService : BaseService, IDashboardService /// learningHubHttpClient. /// The Open Api Http Client. /// logger. - /// MoodleHttpClient. - public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IMoodleHttpClient moodleHttpClient) + /// MoodleApiService. + public DashboardService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, ILogger logger, IMoodleApiService moodleApiService) : base(learningHubHttpClient, openApiHttpClient, logger) { - this.moodleHttpClient = moodleHttpClient; + this.moodleApiService = moodleApiService; } /// @@ -128,14 +125,24 @@ public async Task GetResourcesAsync(string d /// The dashboard type. /// The page Number. /// A representing the result of the asynchronous operation. - public async Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber) + public async Task> GetEnrolledCoursesFromMoodleAsync(int currentUserId, int pageNumber) { - List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient); - viewmodel = await moodleApiService.GetEnrolledCoursesAsync(currentUserId, pageNumber); + List viewmodel = new List { }; + viewmodel = await this.moodleApiService.GetEnrolledCoursesAsync(currentUserId, pageNumber); return viewmodel; } + /// + /// GetEnrolledCoursesFromMoodleAsync. + /// + /// The current User Id type. + /// A representing the result of the asynchronous operation. + public async Task GetMoodleUserIdAsync(int currentUserId) + { + var moodleUserId = await this.moodleApiService.GetMoodleUserIdByUsernameAsync(currentUserId); + return moodleUserId; + } + /// /// Logs Dashboared viewed event. /// diff --git a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs index 068219b42..c80a764f0 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs @@ -2,15 +2,11 @@ { using System; using System.Collections.Generic; - using System.Net.Http; - using System.Text.Json; using System.Threading.Tasks; - using LearningHub.Nhs.Models.Entities.Reporting; - using LearningHub.Nhs.Services.Interface; + using LearningHub.Nhs.Models.Moodle.API; + using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; using Newtonsoft.Json; /// @@ -18,103 +14,101 @@ /// public class MoodleApiService : IMoodleApiService { - private readonly IMoodleHttpClient moodleHttpClient; + private readonly IOpenApiHttpClient openApiHttpClient; + private readonly MoodleApiConfig configuration; /// /// Initializes a new instance of the class. /// - /// moodleHttpClient. - public MoodleApiService(IMoodleHttpClient moodleHttpClient) + /// The Open Api Http Client. + /// configuration. + public MoodleApiService(IOpenApiHttpClient openApiHttpClient, IOptions configuration) { - this.moodleHttpClient = moodleHttpClient; + this.openApiHttpClient = openApiHttpClient; + this.configuration = configuration.Value; } /// - /// GetEnrolledCoursesAsync. + /// GetMoodleUserIdByUsernameAsync. /// - /// Moodle user id. - /// The page Number. - /// A representing the result of the asynchronous operation. - public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber) + /// current User Id. + /// UserId from Moodle. + public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) { - List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient); - - var client = await this.moodleHttpClient.GetClient(); - string additionalParameters = $"userid={userId}"; - string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); - string url = $"&wsfunction=core_enrol_get_users_courses&{additionalParameters}"; - - HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url); + int moodleUserId = 0; - if (response.IsSuccessStatusCode) + try { - var result = response.Content.ReadAsStringAsync().Result; + var client = await this.openApiHttpClient.GetClientAsync(); - using var document = JsonDocument.Parse(result); - var root = document.RootElement; + var request = $"Moodle/GetMoodleUserId/{currentUserId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); - // Check if it's a JSON object and contains "exception" - if (!(root.ValueKind == JsonValueKind.Object && root.TryGetProperty("exception", out _))) + if (response.IsSuccessStatusCode) { - viewmodel = JsonConvert.DeserializeObject>(result); - - foreach (var course in viewmodel) - { - course.CourseCompletionViewModel = await moodleApiService.GetCourseCompletionAsync(userId, course.Id.Value, pageNumber); - } + var result = response.Content.ReadAsStringAsync().Result; + moodleUserId = JsonConvert.DeserializeObject(result); } - else + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) { - // Contains error, handle it as needed. + throw new Exception("AccessDenied"); } + + return moodleUserId; } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || - response.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (Exception ex) { - throw new Exception("AccessDenied"); + // this.Logger.LogError(string.Format("Error occurred in GetSearchResultAsync: {0}", ex.Message)); + return moodleUserId; } - - return viewmodel; } /// /// GetEnrolledCoursesAsync. /// - /// Moodle user id. - /// Moodle course id. - /// pageNumber. - /// List of MoodleCourseResponseViewModel. - public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber) + /// Moodle user id. + /// The page Number. + /// A representing the result of the asynchronous operation. + public async Task> GetEnrolledCoursesAsync(int currentUserId, int pageNumber) { - MoodleCourseCompletionViewModel viewmodel = new MoodleCourseCompletionViewModel { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient); + List viewmodel = new List(); - var client = await this.moodleHttpClient.GetClient(); - string additionalParameters = $"userid={userId}&courseid={courseId}"; - string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); - string url = $"&wsfunction=core_completion_get_course_completion_status&{additionalParameters}"; - - HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url); - - if (response.IsSuccessStatusCode) + try { - var result = response.Content.ReadAsStringAsync().Result; + var client = await this.openApiHttpClient.GetClientAsync(); - var canViewReport = JsonConvert.DeserializeObject(result); + var request = $"Moodle/GetEnrolledCourses/{currentUserId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); - if (string.IsNullOrEmpty(canViewReport.Exception)) + if (response.IsSuccessStatusCode) { - viewmodel = JsonConvert.DeserializeObject(result); + var result = response.Content.ReadAsStringAsync().Result; + viewmodel = JsonConvert.DeserializeObject>(result); } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return viewmodel; } - else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || - response.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (Exception ex) { - throw new Exception("AccessDenied"); + // this.Logger.LogError(string.Format("Error occurred in GetSearchResultAsync: {0}", ex.Message)); + return viewmodel; } + } - return viewmodel; + /// + /// GetCourseUrl. + /// + /// course Id. + /// return course URL. + public string GetCourseUrl(int courseId) + { + var apiBaseUrl = this.configuration.BaseUrl; + string path = this.configuration.CoursePath; + return $"{apiBaseUrl}{path}?id={courseId}"; } } } diff --git a/LearningHub.Nhs.WebUI/Services/SearchService.cs b/LearningHub.Nhs.WebUI/Services/SearchService.cs index 578a5861f..333848a30 100644 --- a/LearningHub.Nhs.WebUI/Services/SearchService.cs +++ b/LearningHub.Nhs.WebUI/Services/SearchService.cs @@ -164,10 +164,10 @@ public async Task PerformSearch(IPrincipal user, SearchRe { var filter = filters.Where(x => x.DisplayName == filteritem).FirstOrDefault(); - if (filter != null && UtilityHelper.FindwiseResourceMoodleTypeDict.ContainsKey(filter.DisplayName)) + if (filter != null && UtilityHelper.FindwiseResourceTypeDict.ContainsKey(filter.DisplayName)) { - var resourceTypeEnum = UtilityHelper.FindwiseResourceMoodleTypeDict[filter.DisplayName]; - var searchfilter = new SearchFilterModel() { DisplayName = UtilityHelper.GetPrettifiedResourceTypeNameMoodle(resourceTypeEnum), Count = filter.Count, Value = filteritem, Selected = searchRequest.Filters?.Contains(filter.DisplayName) ?? false }; + var resourceTypeEnum = UtilityHelper.FindwiseResourceTypeDict[filter.DisplayName]; + var searchfilter = new SearchFilterModel() { DisplayName = UtilityHelper.GetPrettifiedResourceTypeName(resourceTypeEnum), Count = filter.Count, Value = filteritem, Selected = searchRequest.Filters?.Contains(filter.DisplayName) ?? false }; searchfilters.Add(searchfilter); } } diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index 0a0c04c49..9fd65bd60 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -3,8 +3,6 @@ using System.Net.Http; using GDS.MultiPageFormData; using LearningHub.Nhs.Models.OpenAthens; - using LearningHub.Nhs.Services; - using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; @@ -62,13 +60,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); - services.AddHttpClient() - .ConfigurePrimaryHttpMessageHandler( - () => new HttpClientHandler - { - ServerCertificateCustomValidationCallback = - HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, - }); } else { @@ -76,7 +67,6 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); - services.AddHttpClient(); } // Config @@ -108,6 +98,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml index 68ad72564..a7351226b 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml @@ -1,8 +1,9 @@ @using LearningHub.Nhs.Models.Dashboard +@using LearningHub.Nhs.Models.Moodle.API @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.WebUI.Helpers -@model MoodleCourseResponseViewModel -@inject Microsoft.Extensions.Configuration.IConfiguration Configuration; +@model MoodleCourseResponseModel +@inject LearningHub.Nhs.WebUI.Interfaces.IMoodleApiService moodleApiService; @{ bool providerExists = false; @@ -10,11 +11,7 @@ string GetMoodleCourseUrl(int courseId) { - var apiBaseUrl = Configuration["MoodleAPIConfig:BaseUrl"]; - string path = $"course/view.php"; - string returnUrl = $@"{apiBaseUrl}{path}?id={courseId}"; - - return returnUrl; + return moodleApiService.GetCourseUrl(courseId); } }
diff --git a/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml index 8b8d25f8a..47cf33901 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml @@ -32,8 +32,8 @@ TotalCount = Model.MyLearnings.TotalCount }; - var enableMoodle = this.ViewBag.EnableMoodle; - var validMoodleUser = this.ViewBag.ValidMoodleUser; + bool enableMoodle = (bool?)ViewBag.EnableMoodle ?? false; + bool validMoodleUser = (bool?)ViewBag.ValidMoodleUser ?? false; }

My accessed learning

diff --git a/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml index cffb07772..756908897 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_ResourceTray.cshtml @@ -43,10 +43,6 @@
@@ -62,7 +63,7 @@
    - @if(@Model.Resources.Resources != null) + @if (@Model.Resources.Resources != null) { @foreach (var resource in Model.Resources.Resources) { diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml index 647583787..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 new file mode 100644 index 000000000..20e9b1904 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The Moodle Settings. + /// + public class MoodleConfig + { + /// + /// Gets or sets the base url for the Moodle service. + /// + public string ApiBaseUrl { get; set; } = null!; + + /// + /// Gets or sets the Web service Rest Format. + /// + public string ApiWsRestFormat { get; set; } = null!; + + /// + /// Gets or sets the token. + /// + 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/LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs similarity index 79% rename from LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs index 3348a20a9..4a98b4ebf 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleHttpClient.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs @@ -1,8 +1,8 @@ -namespace LearningHub.Nhs.Services.Interface -{ - using System.Net.Http; - using System.Threading.Tasks; +using System.Net.Http; +using System.Threading.Tasks; +namespace LearningHub.Nhs.OpenApi.Services.Interface.HttpClients +{ /// /// The Moodle Http Client interface. /// @@ -20,4 +20,4 @@ public interface IMoodleHttpClient /// defaultParameters. string GetDefaultParameters(); } -} \ No newline at end of file +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 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 new file mode 100644 index 000000000..ee573d43b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs @@ -0,0 +1,36 @@ +using LearningHub.Nhs.Models.Moodle.API; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + /// + /// IMoodleApiService. + /// + public interface IMoodleApiService + { + /// + /// GetResourcesAsync. + /// + /// 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/LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs similarity index 61% rename from LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs index 79f44027a..2c9fc410a 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleHttpClient.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs @@ -1,34 +1,42 @@ -namespace LearningHub.Nhs.Services -{ - using System; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading.Tasks; - using LearningHub.Nhs.Services.Interface; - using Microsoft.Extensions.Configuration; +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 MoodleHttpClient : IMoodleHttpClient, IDisposable { - private readonly HttpClient httpClient = new (); + private readonly MoodleConfig moodleConfig; + 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. /// /// httpClient. - /// config. - public MoodleHttpClient(HttpClient httpClient, IConfiguration config) + /// config. + public MoodleHttpClient(HttpClient httpClient, IOptions moodleConfig) { + this.moodleConfig = moodleConfig.Value; this.httpClient = httpClient; - this.moodleAPIBaseUrl = config["MoodleAPIConfig:BaseUrl"] + "webservice/rest/server.php"; - this.moodleAPIMoodleWSRestFormat = config["MoodleAPIConfig:MoodleWSRestFormat"]; - this.moodleAPIWSToken = config["MoodleAPIConfig:WSToken"]; + + this.moodleApiUrl = this.moodleConfig.ApiBaseUrl + this.moodleConfig.ApiPath; + this.moodleApiMoodleWsRestFormat = this.moodleConfig.ApiWsRestFormat; + this.moodleApiWsToken = this.moodleConfig.ApiWsToken; } /// @@ -37,7 +45,7 @@ public MoodleHttpClient(HttpClient httpClient, IConfiguration config) /// The . public async Task GetClient() { - this.Initialise(this.moodleAPIBaseUrl); + this.Initialise(this.moodleApiUrl); return this.httpClient; } @@ -47,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 new file mode 100644 index 000000000..08786db8d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -0,0 +1,192 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using LearningHub.Nhs.Models.Moodle.API; + 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; + + /// + /// MoodleApiService. + /// + public class MoodleApiService : IMoodleApiService + { + private readonly IMoodleHttpClient moodleHttpClient; + private readonly ILogger logger; + + /// + /// Initializes a new instance of the class. + /// + /// moodleHttpClient. + /// logger. + public MoodleApiService(IMoodleHttpClient moodleHttpClient, ILogger logger) + { + this.moodleHttpClient = moodleHttpClient; + this.logger = logger; + } + + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// current User Id. + /// UserId from Moodle. + public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) + { + var parameters = new Dictionary + { + { "criteria[0][key]", "username" }, + { "criteria[0][value]", currentUserId.ToString() } + }; + + var response = await GetCallMoodleApiAsync("core_user_get_users", parameters); + + var user = response?.Users?.FirstOrDefault(u => u.Username == currentUserId.ToString()); + return user?.Id ?? 0; + } + + + /// + /// 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 string + var queryBuilder = new StringBuilder($"&wsfunction={wsFunction}"); + foreach (var param in parameters) + { + queryBuilder.Append($"&{param.Key}={Uri.EscapeDataString(param.Value)}"); + } + + string fullUrl = "?" + defaultParameters + queryBuilder.ToString(); + + HttpResponseMessage response = await client.GetAsync(fullUrl); + string result = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + // 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) + { + this.logger.LogError($"Moodle API access denied. Status Code: {response.StatusCode}"); + throw new Exception("AccessDenied to MoodleApi"); + } + else + { + this.logger.LogError($"Moodle API error. Status Code: {response.StatusCode}, Response: {result}"); + throw new Exception("Error with MoodleApi"); + } + } + + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index fb9098bde..f00f13253 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -23,6 +23,7 @@ public static class Startup public static void AddServices(this IServiceCollection services) { services.AddScoped(); + services.AddHttpClient(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -33,6 +34,7 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs index 63b61f042..7d5a61795 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Configuration/ConfigurationExtensions.cs @@ -40,6 +40,11 @@ public static class ConfigurationExtensions /// public const string AzureSectionName = "Azure"; + /// + /// The FindwiseSectionName. + /// + public const string MoodleSectionName = "Moodle"; + /// /// Adds config. /// @@ -58,6 +63,8 @@ public static void AddConfig(this IServiceCollection services, IConfiguration co services.AddOptions().Bind(config.GetSection(LearningHubApiSectionName)); services.AddOptions().Bind(config.GetSection(AzureSectionName)); + + services.AddOptions().Bind(config.GetSection(MoodleSectionName)); } private static OptionsBuilder RegisterPostConfigure(this OptionsBuilder builder) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs new file mode 100644 index 000000000..f2639d005 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs @@ -0,0 +1,70 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Options; + using System.Threading.Tasks; + + /// + /// Moodle operations. + /// + [Route("Moodle")] + [ApiController] + [Authorize] + public class MoodleController : Controller + { + private readonly IMoodleApiService moodleService; + + /// + /// Initializes a new instance of the class. + /// + /// The moodle service. + public MoodleController(IMoodleApiService moodleService) + { + this.moodleService = moodleService; + } + + /// + /// The GetMoodleUserId. + /// + /// The LH user id. + /// The . + [HttpGet] + [Route("GetMoodleUserId/{currentUserId?}")] + public async Task GetMoodleUserId(int? currentUserId) + { + if (currentUserId.HasValue) + { + var moodleUser = await this.moodleService.GetMoodleUserIdByUsernameAsync(currentUserId.Value); + return this.Ok(moodleUser); + } + else + { + 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 401183bf6..181c8eaf8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -119,5 +119,10 @@ "ResponseType": "code id_token", "AuthSecret": "", "AuthTimeout": 20 + }, + "Moodle": { + "ApiBaseUrl": "", + "ApiWsRestFormat": "json", + "ApiWsToken": "" } }