From 04d9db2926e8c65701a675f724ca9e80fdd1b6cc Mon Sep 17 00:00:00 2001 From: Frank C <115793157+frank-hee@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:08:03 +0100 Subject: [PATCH 1/3] fixed banner image and checkbox scss bug --- .../Styles/nhsuk/layout.scss | 5 +- .../Styles/nhsuk/nhsuk-overrides.scss | 253 ++++++++---------- 2 files changed, 120 insertions(+), 138 deletions(-) diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss index c839722f..25b84b7d 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss @@ -110,14 +110,11 @@ li.autosuggestion-option:last-of-type { top: 100%; } } - /* tablet */ @media (max-width: px2rem(768)) { - .autosuggestion-menu { top: 100%; } - } /* mobile */ @@ -125,4 +122,4 @@ li.autosuggestion-option:last-of-type { .autosuggestion-menu { top: 100%; } -} \ No newline at end of file +} diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss index 98dba42d..22c31975 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/nhsuk-overrides.scss @@ -179,7 +179,7 @@ form label.nhsuk-u-visually-hidden { // Below taken from layout.scss // Overrides largely due to -// - Highly customised header +// - Highly customised header // - Use of full browser width hero images and full width colour bands // - Beta banner // @@ -195,128 +195,142 @@ form label.nhsuk-u-visually-hidden { } // Header items - .nhsuk-header { - padding: 0 px2rem(32); - } +.nhsuk-header { + padding: 0 px2rem(32); +} - .nhsuk-header .nhsuk-width-container.app-width-container { - max-width: px2rem(1144); - margin: 0 auto; - } +.nhsuk-header .nhsuk-width-container.app-width-container { + max-width: px2rem(1144); + margin: 0 auto; +} - .nhsuk-width-container.app-width-container.beta-banner { - padding: px2rem(8) px2rem(32); - max-width: px2rem(1208); - margin: 0 auto; - } +.nhsuk-width-container.app-width-container.beta-banner { + padding: px2rem(8) px2rem(32); + max-width: px2rem(1208); + margin: 0 auto; +} - .nhsuk-header .nhsuk-header__container::after { - content: none; - } +.nhsuk-header .nhsuk-header__container::after { + content: none; +} - .nhsuk-header__navigation.app-width-container { - max-width: px2rem(1144); - } +.nhsuk-header__navigation.app-width-container { + max-width: px2rem(1144); +} - .nhsuk-header__container.app-width-container { - display: flex; - justify-content: space-between; - gap: 0 px2rem(24); - padding: px2rem(16) 0; - } +.nhsuk-header__container.app-width-container { + display: flex; + justify-content: space-between; + gap: 0 px2rem(24); + padding: px2rem(16) 0; +} - .nhsuk-header__logo { - flex: 1 0 0; - } +.nhsuk-header__logo { + flex: 1 0 0; +} - .nhsuk-header__logo .nhsuk-header__link--service { - display: inline-flex; - } +.nhsuk-header__logo .nhsuk-header__link--service { + display: inline-flex; +} - .nhsuk-header__service-name { - font-size: px2rem(19); - } +.nhsuk-header__service-name { + font-size: px2rem(19); +} - .nhsuk-header__notification-dot { - position: absolute; - top: px2rem(8); - right: px2rem(-10); - font-size: px2rem(11); - line-height: px2rem(18); - font-weight: 900; - background: $nhsuk-error-color; - color: white; - min-width: px2rem(18); - height: px2rem(18); - text-align: center; - border-radius: px2rem(9); - padding: px2rem(1) px2rem(3) 0; - } +.nhsuk-header__notification-dot { + position: absolute; + top: px2rem(8); + right: px2rem(-10); + font-size: px2rem(11); + line-height: px2rem(18); + font-weight: 900; + background: $nhsuk-error-color; + color: white; + min-width: px2rem(18); + height: px2rem(18); + text-align: center; + border-radius: px2rem(9); + padding: px2rem(1) px2rem(3) 0; +} + +.nhsuk-header__menu { + display: none; +} - .nhsuk-header__menu { - display: none; - } +.nhsuk-header__search .nhsuk-search__input { + width: px2rem(260); +} - .nhsuk-header__search .nhsuk-search__input { +.nhsuk-header__search { + .nhsuk-search__input { width: px2rem(260); - } - - .nhsuk-header__search { - .nhsuk-search__input { - width: px2rem(260); - &::-moz-placeholder { - opacity: 1; - } - } - - #search > label.nhsuk-u-visually-hidden { - background-color: $nhsuk-white; + &::-moz-placeholder { + opacity: 1; } } - .nhsuk-header__break { - display: none; + #search > label.nhsuk-u-visually-hidden { + background-color: $nhsuk-white; } +} - .nhsuk-header__mobile-only-nav { - display: none; - } +#header-dropdown-menu-control { + opacity: 0; + position: absolute; +} - .nhsuk-header__mobile-break { - display: none; - } - .nhsuk-header__navigation-item--current { - a { - font-weight: bold; - } - } +#header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { + display: block +} - .nhsuk-account__login { - // also a header item - font-size: px2rem(14); - float: right; - position: relative; - z-index: 2; - display: flex; - justify-content: space-between; - align-items: center; - gap: px2rem(24); - } +#header-mobile-search-control { + display: none; +} - .nhsuk-account__login--link, - .nhsuk-account__login--link:visited, - .nhsuk-account__login--link:hover { - // all header items - color: #fff; +.nhsuk-header__break { + display: none; +} + +.nhsuk-header__mobile-only-nav { + display: none; +} + +.nhsuk-header__mobile-break { + display: none; +} + +.nhsuk-header__navigation-item--current { + a { + font-weight: bold; } +} + +.nhsuk-account__login { + // also a header item + font-size: px2rem(14); + float: right; + position: relative; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + gap: px2rem(24); +} + +.nhsuk-account__login--link, +.nhsuk-account__login--link:visited, +.nhsuk-account__login--link:hover { + // all header items + color: #fff; +} // End of header items -.app-width-container--full { +.nhsuk-width-container.app-width-container--full { // used to allow placement of hero (full width) images margin: 0; max-width: none @@ -354,7 +368,7 @@ form label.nhsuk-u-visually-hidden { /* small desktop */ @media (max-width: px2rem(989)) { - //entirely headers + //entirely headers .nhsuk-header__container { flex-wrap: wrap; @@ -385,6 +399,13 @@ form label.nhsuk-u-visually-hidden { order: 0; } + .nhsuk-account__login { + // also part of the header + order: 1; + margin-left: auto; + margin-right: 0px; + } + .nhsuk-header__break { display: block; width: 100%; @@ -461,14 +482,6 @@ form label.nhsuk-u-visually-hidden { box-shadow: 0 0 0 2px white; z-index: 10; } - - .nhsuk-account__login { - // also part of the header - order: 1; - margin-left: auto; - margin-right: 0px; - } - } @media (max-width: px2rem(768)) { @@ -510,19 +523,6 @@ form label.nhsuk-u-visually-hidden { margin-left: px2rem(12); } - #header-dropdown-menu-control { - opacity: 0; - position: absolute; - } - - #header-dropdown-menu-control:checked ~ .nhsuk-header__navigation:not(.js-show) { - display: block - } - - #header-mobile-search-control { - display: none; - } - .nhsuk-header__break { display: none; } @@ -570,21 +570,6 @@ form label.nhsuk-u-visually-hidden { margin: px2rem(16) px2rem(-16) 0; border-bottom: 1px solid $color_nhsuk-grey-4; } - - #header-mobile-search-control { - display: block; - opacity: 0; - position: absolute; - } - - #header-mobile-search-control:checked ~ .nhsuk-header__search .nhsuk-header__search-wrap { - display: block; - } - - .nhsuk-width-container.nhsuk-header__container.app-width-container { - padding-bottom: 0; - } - } @media (max-width: px2rem(640)) { @@ -670,7 +655,7 @@ form label.nhsuk-u-visually-hidden { margin: px2rem(16) px2rem(-16) 0; border-bottom: 1px solid $color_nhsuk-grey-4; } - + #header-mobile-search-control { display: block; opacity: 0; @@ -684,4 +669,4 @@ form label.nhsuk-u-visually-hidden { .nhsuk-width-container.nhsuk-header__container.app-width-container { padding-bottom: 0; } -} \ No newline at end of file +} From bcd5659d9e114ac9acffc0914a15270a87e02506 Mon Sep 17 00:00:00 2001 From: Binon Date: Wed, 30 Jul 2025 14:53:52 +0100 Subject: [PATCH 2/3] bit of moodle refactoring and reload the moodle user --- .../Controllers/HomeController.cs | 37 ++++++-- .../Helpers/UtilityHelper.cs | 54 +++++------ .../Interfaces/IDashboardService.cs | 10 +- .../Interfaces/IMoodleApiService.cs | 19 ++-- .../LearningHub.Nhs.WebUI.csproj | 2 +- .../Models/DashboardViewModel.cs | 3 +- .../Services/DashboardService.cs | 19 +++- .../Services/MoodleApiService.cs | 64 ++++++++++--- .../Views/Home/_CourseEnrolled.cshtml | 3 +- .../Views/Home/_MyAccessedLearningTray.cshtml | 4 +- .../Views/Search/_ResourceSearchResult.cshtml | 2 +- .../Configuration/MoodleConfig.cs | 29 ++++++ .../HttpClients/IMoodleHttpClient.cs | 23 +++++ .../Services/IMoodleApiService.cs | 21 ++++ .../HttpClients/MoodleHttpClient.cs | 95 +++++++++++++++++++ .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- .../Services/MoodleApiService.cs | 90 ++++++++++++++++++ .../Startup.cs | 2 + .../Configuration/ConfigurationExtensions.cs | 7 ++ .../Controllers/MoodleController.cs | 47 +++++++++ .../LearningHub.Nhs.OpenApi/appsettings.json | 5 + 21 files changed, 472 insertions(+), 66 deletions(-) create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs create mode 100644 OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index d5f53c00..57c170ae 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/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index ef71191f..c07ab98e 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -36,19 +36,19 @@ public static class UtilityHelper /// /// Findwise Moodle resource type dictionary. /// - public static readonly Dictionary FindwiseResourceMoodleTypeDict = new 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 }, + { "video", ResourceTypeEnum.Video }, + { "article", ResourceTypeEnum.Article }, + { "case", ResourceTypeEnum.Case }, + { "weblink", ResourceTypeEnum.WebLink }, + { "audio", ResourceTypeEnum.Audio }, + { "scorm", ResourceTypeEnum.Scorm }, + { "assessment", ResourceTypeEnum.Assessment }, + { "genericfile", ResourceTypeEnum.GenericFile }, + { "image", ResourceTypeEnum.Image }, + { "html", ResourceTypeEnum.Html }, + { "moodle", ResourceTypeEnum.Moodle }, }; /// @@ -173,41 +173,39 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType /// 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) + public static string GetPrettifiedResourceTypeNameMoodle(ResourceTypeEnum resourceType, int? durationInMilliseconds = 0) { switch (resourceType) { - case ResourceTypeEnumMoodle.Assessment: + case ResourceTypeEnum.Assessment: return "Assessment"; - case ResourceTypeEnumMoodle.Article: + case ResourceTypeEnum.Article: return "Article"; - case ResourceTypeEnumMoodle.Audio: + case ResourceTypeEnum.Audio: string durationText = GetDurationText(durationInMilliseconds ?? 0); durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; return "Audio" + durationText; - case ResourceTypeEnumMoodle.Equipment: + case ResourceTypeEnum.Equipment: return "Equipment"; - case ResourceTypeEnumMoodle.Image: + case ResourceTypeEnum.Image: return "Image"; - case ResourceTypeEnumMoodle.Scorm: + case ResourceTypeEnum.Scorm: return "elearning"; - case ResourceTypeEnumMoodle.Video: + case ResourceTypeEnum.Video: durationText = GetDurationText(durationInMilliseconds ?? 0); durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; return "Video" + durationText; - case ResourceTypeEnumMoodle.WebLink: + case ResourceTypeEnum.WebLink: return "Web link"; - case ResourceTypeEnumMoodle.GenericFile: + case ResourceTypeEnum.GenericFile: return "File"; - case ResourceTypeEnumMoodle.Embedded: + case ResourceTypeEnum.Embedded: return "Embedded"; - case ResourceTypeEnumMoodle.Case: + case ResourceTypeEnum.Case: return "Case"; - case ResourceTypeEnumMoodle.Html: + case ResourceTypeEnum.Html: return "HTML"; - case ResourceTypeEnumMoodle.Moodle: - return "Course"; - case ResourceTypeEnumMoodle.Course: + case ResourceTypeEnum.Moodle: return "Course"; default: return "File"; diff --git a/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs b/LearningHub.Nhs.WebUI/Interfaces/IDashboardService.cs index 9eb8c326..70a39d2f 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 d92c01fa..60dc18cb 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs @@ -2,21 +2,28 @@ { 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. @@ -24,7 +31,7 @@ public interface IMoodleApiService /// Moodle user id. /// Moodle course id. /// pageNumber. - /// List of MoodleCourseResponseViewModel. - Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber); + /// List of MoodleCourseResponseModel. + Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber); } } diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index dc2cf661..b69ead58 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 9ac98c7e..d7ddeda0 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/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 607d4f2b..d0470839 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -9,6 +9,7 @@ using LearningHub.Nhs.Models.Dashboard; using LearningHub.Nhs.Models.Entities.Analytics; using LearningHub.Nhs.Models.Entities.Reporting; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; @@ -128,14 +129,26 @@ 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); + List viewmodel = new List { }; + MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.OpenApiHttpClient); viewmodel = await 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) + { + MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.OpenApiHttpClient); + var moodleUserId = await 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 068219b4..fb69d3ee 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs @@ -3,15 +3,15 @@ using System; using System.Collections.Generic; using System.Net.Http; + using System.Text; using System.Text.Json; using System.Threading.Tasks; - using LearningHub.Nhs.Models.Entities.Reporting; + using LearningHub.Nhs.Models.Moodle.API; using LearningHub.Nhs.Services.Interface; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; using Newtonsoft.Json; + using MoodleCourseCompletionModel = Nhs.Models.Moodle.API.MoodleCourseCompletionModel; /// /// MoodleApiService. @@ -19,14 +19,52 @@ public class MoodleApiService : IMoodleApiService { private readonly IMoodleHttpClient moodleHttpClient; + private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. /// /// moodleHttpClient. - public MoodleApiService(IMoodleHttpClient moodleHttpClient) + /// The Open Api Http Client. + public MoodleApiService(IMoodleHttpClient moodleHttpClient, IOpenApiHttpClient openApiHttpClient) { this.moodleHttpClient = moodleHttpClient; + this.openApiHttpClient = openApiHttpClient; + } + + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// current User Id. + /// UserId from Moodle. + public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) + { + int moodleUserId = 0; + + try + { + var client = await this.openApiHttpClient.GetClientAsync(); + + var request = $"Moodle/GetMoodleUserId/{currentUserId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + moodleUserId = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return moodleUserId; + } + catch (Exception ex) + { + // this.Logger.LogError(string.Format("Error occurred in GetSearchResultAsync: {0}", ex.Message)); + return moodleUserId; + } } /// @@ -35,10 +73,10 @@ public MoodleApiService(IMoodleHttpClient moodleHttpClient) /// Moodle user id. /// The page Number. /// A representing the result of the asynchronous operation. - public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber) + public async Task> GetEnrolledCoursesAsync(int userId, int pageNumber) { - List viewmodel = new List { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient); + List viewmodel = new List { }; + MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.openApiHttpClient); var client = await this.moodleHttpClient.GetClient(); string additionalParameters = $"userid={userId}"; @@ -57,7 +95,7 @@ public async Task> GetEnrolledCoursesAsync(i // Check if it's a JSON object and contains "exception" if (!(root.ValueKind == JsonValueKind.Object && root.TryGetProperty("exception", out _))) { - viewmodel = JsonConvert.DeserializeObject>(result); + viewmodel = JsonConvert.DeserializeObject>(result); foreach (var course in viewmodel) { @@ -84,11 +122,11 @@ public async Task> GetEnrolledCoursesAsync(i /// Moodle user id. /// Moodle course id. /// pageNumber. - /// List of MoodleCourseResponseViewModel. - public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber) + /// List of MoodleCourseResponseModel. + public async Task GetCourseCompletionAsync(int userId, int courseId, int pageNumber) { - MoodleCourseCompletionViewModel viewmodel = new MoodleCourseCompletionViewModel { }; - MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient); + MoodleCourseCompletionModel viewmodel = new MoodleCourseCompletionModel { }; + MoodleApiService moodleApiService = new MoodleApiService(this.moodleHttpClient, this.openApiHttpClient); var client = await this.moodleHttpClient.GetClient(); string additionalParameters = $"userid={userId}&courseid={courseId}"; @@ -105,7 +143,7 @@ public async Task GetCourseCompletionAsync(int if (string.IsNullOrEmpty(canViewReport.Exception)) { - viewmodel = JsonConvert.DeserializeObject(result); + viewmodel = JsonConvert.DeserializeObject(result); } } else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml index 68ad7256..f62eea9f 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml @@ -1,7 +1,8 @@ @using LearningHub.Nhs.Models.Dashboard +@using LearningHub.Nhs.Models.Moodle.API @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.WebUI.Helpers -@model MoodleCourseResponseViewModel +@model MoodleCourseResponseModel @inject Microsoft.Extensions.Configuration.IConfiguration Configuration; @{ diff --git a/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml index 8b8d25f8..47cf3390 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/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml index 64758378..81156cd9 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml @@ -93,7 +93,7 @@
Type: - @UtilityHelper.GetPrettifiedResourceTypeNameMoodle(UtilityHelper.ToEnum(item.ResourceType), 0) + @UtilityHelper.GetPrettifiedResourceTypeNameMoodle(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 00000000..de178b6a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/MoodleConfig.cs @@ -0,0 +1,29 @@ +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!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs new file mode 100644 index 00000000..4a98b4eb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/HttpClients/IMoodleHttpClient.cs @@ -0,0 +1,23 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Services.Interface.HttpClients +{ + /// + /// The Moodle Http Client interface. + /// + public interface IMoodleHttpClient + { + /// + /// The get cient async. + /// + /// The . + Task GetClient(); + + /// + /// GetDefaultParameters. + /// + /// defaultParameters. + string GetDefaultParameters(); + } +} 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 00000000..e366a437 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IMoodleApiService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs new file mode 100644 index 00000000..73af9397 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/HttpClients/MoodleHttpClient.cs @@ -0,0 +1,95 @@ +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 MoodleConfig moodleConfig; + private readonly HttpClient httpClient = new(); + + private bool initialised = false; + private string moodleAPIBaseUrl; + private string moodleAPIMoodleWSRestFormat; + private string moodleAPIWSToken; + + /// + /// Initializes a new instance of the class. + /// + /// httpClient. + /// config. + public MoodleHttpClient(HttpClient httpClient, IOptions moodleConfig) + { + 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; + } + + /// + /// The Get Client method. + /// + /// The . + public async Task GetClient() + { + this.Initialise(this.moodleAPIBaseUrl); + return this.httpClient; + } + + /// + /// GetDefaultParameters. + /// + /// defaultParameters. + public string GetDefaultParameters() + { + string defaultParameters = $"wstoken={this.moodleAPIWSToken}" + + $"&moodlewsrestformat={this.moodleAPIMoodleWSRestFormat}"; + + return defaultParameters; + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// The dispoase. + /// + /// disposing. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.httpClient.Dispose(); + } + } + + private void Initialise(string httpClientUrl) + { + if (this.initialised == false) + { + this.httpClient.BaseAddress = new Uri(httpClientUrl); + this.httpClient.DefaultRequestHeaders.Accept.Clear(); + this.httpClient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + this.initialised = true; + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index 9002a023..bf7b23c2 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 00000000..90ffc428 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/MoodleApiService.cs @@ -0,0 +1,90 @@ +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.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; + } + + + 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}"); + + 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) + { + return JsonConvert.DeserializeObject(result); + } + 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}, Message: {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 fb9098bd..f00f1325 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 63b61f04..7d5a6179 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 00000000..30cb31a4 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/MoodleController.cs @@ -0,0 +1,47 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + 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); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 401183bf..61417224 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": "" } } From 60f1317bf214bfd1d02925a5090f7eb554c5f66e Mon Sep 17 00:00:00 2001 From: Binon Date: Wed, 30 Jul 2025 15:22:55 +0100 Subject: [PATCH 3/3] Rolling back Models to 48 to fix the build --- LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj | 2 +- .../LearningHub.Nhs.OpenApi.Services.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index b69ead58..dc2cf661 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index bf7b23c2..9002a023 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 @@ - +