diff --git a/.gitignore b/.gitignore index 8cc7d8bbe..3b61a883d 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,8 @@ obj /AdminUI/LearningHub.Nhs.AdminUI/web.config /LearningHub.Nhs.WebUI/web.config /WebAPI/LearningHub.Nhs.API/web.config +/LearningHub.Nhs.WebUI/nuget.config +/LearningHub.Nhs.WebUI.BlazorClient/Properties/launchSettings.json +/LearningHub.Nhs.WebUI.BlazorClient/wwwroot/appsettings.json +/LearningHub.Nhs.WebUI.BlazorClient/wwwroot/appsettings.Development.json +/LearningHub.Nhs.WebUI.BlazorClient/nuget.config diff --git a/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs b/LearningHub.Nhs.Shared/Configuration/ExposableFindwiseSettings.cs similarity index 52% rename from LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs rename to LearningHub.Nhs.Shared/Configuration/ExposableFindwiseSettings.cs index 683d5b4c9..0b915bdd7 100644 --- a/LearningHub.Nhs.WebUI/Configuration/FindwiseSettings.cs +++ b/LearningHub.Nhs.Shared/Configuration/ExposableFindwiseSettings.cs @@ -1,9 +1,15 @@ -namespace LearningHub.Nhs.WebUI.Configuration +namespace LearningHub.Nhs.Shared.Configuration { + using LearningHub.Nhs.Shared.Interfaces.Configuration; /// - /// Defines the . + /// Represents a public-facing set of configuration values for Findwise search, + /// intended to be safely exposed to client-side applications or public APIs. + /// + /// + /// Contains only non-sensitive data such as page sizes for various search types. + /// /// - public class FindwiseSettings + public class ExposableFindwiseSettings : IExposableFindwiseSettings { /// /// Gets or sets the ResourceSearchPageSize. diff --git a/LearningHub.Nhs.Shared/Configuration/ExposableSettings.cs b/LearningHub.Nhs.Shared/Configuration/ExposableSettings.cs new file mode 100644 index 000000000..25a82505b --- /dev/null +++ b/LearningHub.Nhs.Shared/Configuration/ExposableSettings.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.Shared.Configuration +{ + using LearningHub.Nhs.Shared.Interfaces.Configuration; + /// + /// Represents configuration values that are safe to expose to clientside frontend applications + /// (such as Blazor WebAssembly) or public-facing APIs. + /// + /// + /// Implements and contains only non-sensitive, non-secret + /// values such as public API endpoints and pagination settings. This separation ensures + /// that secure or private configuration data is not inadvertently exposed to clients. + /// + /// + public class ExposableSettings : IExposableSettings + { + /// + public string LearningHubApiUrl { get; set; } + + /// + /// Gets or sets the UserApiUrl. + /// + public string UserApiUrl { get; set; } + + /// + /// Gets or sets the OpenApiUrl. + /// + public string OpenApiUrl { get; set; } + /// + /// Backend for Frontend (BFF) URL for the Learning Hub API accessed by samesite cookie and uses httpclients with bearers to access external apis. + /// + public string LearningHubApiBFFUrl { get; set; } + /// + public IExposableFindwiseSettings FindwiseSettings { get; set; } + + } +} diff --git a/LearningHub.Nhs.Shared/Helpers/FormattingHelper.cs b/LearningHub.Nhs.Shared/Helpers/FormattingHelper.cs new file mode 100644 index 000000000..1afb7b464 --- /dev/null +++ b/LearningHub.Nhs.Shared/Helpers/FormattingHelper.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.Shared.Helpers +{ + public static class FormattingHelper + { + + /// + /// Returns a number of milliseconds converted into a duration string, such as "10 min 15 sec". Includes rounding to match the behaviour of the Azure Media Player. + /// + /// The number of milliseconds. + /// The duration string. + public static string GetDurationText(int durationInMilliseconds) + { + if (durationInMilliseconds > 0) + { + // Azure media player rounds duration to nearest second. e.g. 8:59.88 becomes 9:00. LH needs to match. + int nearestSecond = (int)Math.Round(((double)durationInMilliseconds) / 1000); + var duration = new TimeSpan(0, 0, nearestSecond); + string returnValue = string.Empty; + + // If duration greater than an hour, don't return the seconds part. + if (duration.Hours > 0) + { + returnValue = $"{duration.Hours} hr {duration.Minutes} min "; + + // Exclude "0 min" from the return value. + if (returnValue.EndsWith(" 0 min ")) + { + returnValue = returnValue.Replace("0 min ", string.Empty); + } + } + else + { + returnValue = $"{duration.Minutes} min {duration.Seconds} sec "; + + // Exclude "0 min" and "0 sec" from the return value. + if (returnValue.StartsWith("0 min ")) + { + returnValue = returnValue.Replace("0 min ", string.Empty); + } + + if (returnValue.EndsWith(" 0 sec ")) + { + returnValue = returnValue.Replace("0 sec ", string.Empty); + } + } + + return returnValue; + } + else + { + return string.Empty; + } + } + } +} diff --git a/LearningHub.Nhs.Shared/Helpers/MoodleHelper.cs b/LearningHub.Nhs.Shared/Helpers/MoodleHelper.cs new file mode 100644 index 000000000..b8d5b3b5d --- /dev/null +++ b/LearningHub.Nhs.Shared/Helpers/MoodleHelper.cs @@ -0,0 +1,115 @@ +using LearningHub.Nhs.Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.Shared.Helpers +{ + public static class MoodleHelper + { + /// 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(ResourceTypeEnum resourceType, int? durationInMilliseconds = 0) + { + switch (resourceType) + { + case ResourceTypeEnum.Assessment: + return "Assessment"; + case ResourceTypeEnum.Article: + return "Article"; + case ResourceTypeEnum.Audio: + string durationText = FormattingHelper.GetDurationText(durationInMilliseconds ?? 0); + durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; + return "Audio" + durationText; + case ResourceTypeEnum.Equipment: + return "Equipment"; + case ResourceTypeEnum.Image: + return "Image"; + case ResourceTypeEnum.Scorm: + return "elearning"; + case ResourceTypeEnum.Video: + durationText = FormattingHelper.GetDurationText(durationInMilliseconds ?? 0); + durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; + return "Video" + durationText; + case ResourceTypeEnum.WebLink: + return "Web link"; + case ResourceTypeEnum.GenericFile: + return "File"; + case ResourceTypeEnum.Embedded: + return "Embedded"; + case ResourceTypeEnum.Case: + return "Case"; + case ResourceTypeEnum.Html: + return "HTML"; + case ResourceTypeEnum.Moodle: + return "Course"; + default: + return "File"; + } + } + + /// 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", 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 }, + }; + + /// + /// Returns a prettified resource type name, suitable for display in the UI. Excludes video/audio duration string. + /// + /// The resource type. + /// The resource type name, and duration if applicable. + public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType) + { + switch (resourceType) + { + case ResourceTypeEnum.Assessment: + return "Assessment"; + case ResourceTypeEnum.Article: + return "Article"; + case ResourceTypeEnum.Audio: + return "Audio"; + case ResourceTypeEnum.Equipment: + return "Equipment"; + case ResourceTypeEnum.Image: + return "Image"; + case ResourceTypeEnum.Scorm: + return "elearning"; + case ResourceTypeEnum.Video: + return "Video"; + case ResourceTypeEnum.WebLink: + return "Web link"; + case ResourceTypeEnum.GenericFile: + return "File"; + case ResourceTypeEnum.Embedded: + return "Embedded"; + case ResourceTypeEnum.Case: + return "Case"; + case ResourceTypeEnum.Html: + return "HTML"; + default: + return "File"; + } + } + } +} diff --git a/LearningHub.Nhs.WebUI/Helpers/ResourceAccessLevelHelper.cs b/LearningHub.Nhs.Shared/Helpers/ResourceAccessLevelHelper.cs similarity index 89% rename from LearningHub.Nhs.WebUI/Helpers/ResourceAccessLevelHelper.cs rename to LearningHub.Nhs.Shared/Helpers/ResourceAccessLevelHelper.cs index e03200172..45eeec5ec 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ResourceAccessLevelHelper.cs +++ b/LearningHub.Nhs.Shared/Helpers/ResourceAccessLevelHelper.cs @@ -1,7 +1,6 @@ -namespace LearningHub.Nhs.WebUI.Helpers +namespace LearningHub.Nhs.Shared.Helpers { using LearningHub.Nhs.Models.Enums; - using Microsoft.AspNetCore.Mvc.Rendering; /// /// Defines the . @@ -11,7 +10,7 @@ public static class ResourceAccessLevelHelper /// /// Get resource access level text. /// - /// The htmlhelper. + /// The enum value to get display text for. /// The . public static string GetResourceAccessLevelText(this ResourceAccessibilityEnum resourceAccessibilityEnum) { diff --git a/LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs b/LearningHub.Nhs.Shared/Helpers/ResourceTypeEnumMoodle.cs similarity index 96% rename from LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs rename to LearningHub.Nhs.Shared/Helpers/ResourceTypeEnumMoodle.cs index e0601d156..741461664 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ResourceTypeEnumMoodle.cs +++ b/LearningHub.Nhs.Shared/Helpers/ResourceTypeEnumMoodle.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Helpers; +namespace LearningHub.Nhs.Shared.Helpers; /// /// Represents the types of resources available in Moodle. diff --git a/LearningHub.Nhs.WebUI/Helpers/SearchHelper.cs b/LearningHub.Nhs.Shared/Helpers/SearchHelper.cs similarity index 96% rename from LearningHub.Nhs.WebUI/Helpers/SearchHelper.cs rename to LearningHub.Nhs.Shared/Helpers/SearchHelper.cs index c94eda513..f7d4bd593 100644 --- a/LearningHub.Nhs.WebUI/Helpers/SearchHelper.cs +++ b/LearningHub.Nhs.Shared/Helpers/SearchHelper.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Helpers +namespace LearningHub.Nhs.Shared.Helpers { using System.Collections.Generic; using LearningHub.Nhs.Models.Enums; diff --git a/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableFindwiseSettings.cs b/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableFindwiseSettings.cs new file mode 100644 index 000000000..3c3a9691a --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableFindwiseSettings.cs @@ -0,0 +1,29 @@ +namespace LearningHub.Nhs.Shared.Interfaces.Configuration +{ + /// + /// Represents configuration values related to Findwise search that are safe to expose + /// to client-side applications or public-facing APIs. + /// + /// + /// This includes non-sensitive values such as page sizes for different types of search results. + /// It does not contain any secure credentials or internal service configuration. + /// + /// + public interface IExposableFindwiseSettings + { + /// + /// Gets or sets the page size for resource search results. + /// + public int ResourceSearchPageSize { get; set; } + + /// + /// Gets or sets the CatalogueSearchPageSize. + /// + public int CatalogueSearchPageSize { get; set; } + + /// + /// Gets or sets the AllCatalogueSearchPageSize. + /// + public int AllCatalogueSearchPageSize { get; set; } + } +} diff --git a/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableSettings.cs b/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableSettings.cs new file mode 100644 index 000000000..753858fba --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Configuration/IExposableSettings.cs @@ -0,0 +1,42 @@ +namespace LearningHub.Nhs.Shared.Interfaces.Configuration +{ + /// + /// Defines a contract for configuration data that is non-sensitive and safe to expose publicly + /// + /// + /// This interface exposes only data that is safe to be publicly consumed or shared, + /// such as API endpoint URLs or non-sensitive configuration values. + /// It explicitly excludes any private or sensitive information (e.g., authentication tokens, + /// credentials, or secret keys), which should be handled via separate interfaces or services. + /// + /// + /// + /// The data provided by this interface can be safely used in frontend technologies, + /// such as Blazor WebAssembly, JavaScript frameworks, or other client-side applications, + /// without risking exposure of sensitive information. + /// + /// + public interface IExposableSettings + { + /// + /// Gets or sets the LearningHubApiUrl. + /// + public string LearningHubApiUrl { get; set; } + + /// + /// Gets or sets the UserApiUrl. + /// + public string UserApiUrl { get; set; } + + /// + /// Gets or sets the OpenApiUrl. + /// + public string OpenApiUrl { get; set; } + /// + /// Gets or sets the LearningHubApiBFFUrl used to proxy via same domain cookie to the BFF LearningHubAPI calls. + /// + public string LearningHubApiBFFUrl { get; set; } + + public IExposableFindwiseSettings FindwiseSettings { get; set; } + } +} diff --git a/LearningHub.Nhs.Shared/Interfaces/Http/IAPIHttpClient.cs b/LearningHub.Nhs.Shared/Interfaces/Http/IAPIHttpClient.cs new file mode 100644 index 000000000..c65fe674b --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Http/IAPIHttpClient.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.Shared.Interfaces.Http +{ + /// + /// Represents an HTTP client for a specific API. + /// + public interface IAPIHttpClient + { + /// + /// Gets the configured for the API. + /// + Task GetClientAsync(); + + /// + /// Gets the base URL of the API. + /// + string ApiUrl { get; } + } +} diff --git a/LearningHub.Nhs.Shared/Interfaces/Http/ILearningHubHttpClient.cs b/LearningHub.Nhs.Shared/Interfaces/Http/ILearningHubHttpClient.cs new file mode 100644 index 000000000..68ebb245b --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Http/ILearningHubHttpClient.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.Shared.Interfaces.Http +{ + /// + /// Marker interface for the LearningHub API HttpClient. + /// + /// + /// Inherits from to enable + /// dependency injection of a specific implementation configured with + /// different API endpoints or settings specific to LH API. + /// + /// + /// + /// Currently, this interface is empty and used solely to differentiate implementations + /// that connect to different endpoints via configuration, but it may be extended in the future + /// with LearningHub-specific functionality or properties. + /// + /// + public interface ILearningHubHttpClient : IAPIHttpClient + { + + } +} diff --git a/LearningHub.Nhs.Shared/Interfaces/Http/IOpenAPIHttpClient.cs b/LearningHub.Nhs.Shared/Interfaces/Http/IOpenAPIHttpClient.cs new file mode 100644 index 000000000..cffdea50a --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Http/IOpenAPIHttpClient.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.Shared.Interfaces.Http +{ + /// + /// Marker interface for the IOpenAPIHttpClient API HttpClient. + /// + /// + /// Inherits from to enable + /// dependency injection of a specific implementation configured with + /// a openapi-related API endpoint or settings. + /// + /// + /// + /// This interface is currently empty and used solely to differentiate + /// implementations that connect to different endpoints via configuration. + /// It may be extended in the future with user-specific functionality or properties. + /// + /// + public interface IOpenApiHttpClient : IAPIHttpClient + { + + } +} diff --git a/LearningHub.Nhs.Shared/Interfaces/Http/IUserAPIHttpClient.cs b/LearningHub.Nhs.Shared/Interfaces/Http/IUserAPIHttpClient.cs new file mode 100644 index 000000000..25ec29d70 --- /dev/null +++ b/LearningHub.Nhs.Shared/Interfaces/Http/IUserAPIHttpClient.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.Shared.Interfaces.Http +{ + /// + /// Marker interface for the User API HttpClient. + /// + /// + /// Inherits from to enable + /// dependency injection of a specific implementation configured with + /// a user-related API endpoint or settings. + /// + /// + /// + /// This interface is currently empty and used solely to differentiate + /// implementations that connect to different endpoints via configuration. + /// It may be extended in the future with user-specific functionality or properties. + /// + /// + public interface IUserApiHttpClient : IAPIHttpClient + { + + } +} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IProviderService.cs b/LearningHub.Nhs.Shared/Interfaces/Services/IProviderService.cs similarity index 95% rename from LearningHub.Nhs.WebUI/Interfaces/IProviderService.cs rename to LearningHub.Nhs.Shared/Interfaces/Services/IProviderService.cs index 528b7a4ec..acd3321ce 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IProviderService.cs +++ b/LearningHub.Nhs.Shared/Interfaces/Services/IProviderService.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Interfaces +namespace LearningHub.Nhs.Shared.Interfaces.Services { using System.Collections.Generic; using System.Threading.Tasks; diff --git a/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs b/LearningHub.Nhs.Shared/Interfaces/Services/ISearchService.cs similarity index 97% rename from LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs rename to LearningHub.Nhs.Shared/Interfaces/Services/ISearchService.cs index 3f083bc38..8dba5d177 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ISearchService.cs +++ b/LearningHub.Nhs.Shared/Interfaces/Services/ISearchService.cs @@ -1,10 +1,10 @@ -namespace LearningHub.Nhs.WebUI.Interfaces +namespace LearningHub.Nhs.Shared.Interfaces.Services { + using LearningHub.Nhs.Shared.Models.Search; + using LearningHub.Nhs.Models.Search.SearchClick; using System.Security.Principal; using System.Threading.Tasks; using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Models.Search.SearchClick; - using LearningHub.Nhs.WebUI.Models.Search; /// /// Defines the . diff --git a/LearningHub.Nhs.Shared/LearningHub.Nhs.Shared.csproj b/LearningHub.Nhs.Shared/LearningHub.Nhs.Shared.csproj new file mode 100644 index 000000000..11a4de7fa --- /dev/null +++ b/LearningHub.Nhs.Shared/LearningHub.Nhs.Shared.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/LearningHub.Nhs.WebUI/Models/Search/SearchActionCatalogueClickModel.cs b/LearningHub.Nhs.Shared/Models/Search/SearchActionCatalogueClickModel.cs similarity index 88% rename from LearningHub.Nhs.WebUI/Models/Search/SearchActionCatalogueClickModel.cs rename to LearningHub.Nhs.Shared/Models/Search/SearchActionCatalogueClickModel.cs index a85e334d5..1129dd78e 100644 --- a/LearningHub.Nhs.WebUI/Models/Search/SearchActionCatalogueClickModel.cs +++ b/LearningHub.Nhs.Shared/Models/Search/SearchActionCatalogueClickModel.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Models +namespace LearningHub.Nhs.Shared.Models.Search { using LearningHub.Nhs.Models.Search; diff --git a/LearningHub.Nhs.WebUI/Models/Search/SearchFormActionTypeEnum.cs b/LearningHub.Nhs.Shared/Models/Search/SearchFormActionTypeEnum.cs similarity index 96% rename from LearningHub.Nhs.WebUI/Models/Search/SearchFormActionTypeEnum.cs rename to LearningHub.Nhs.Shared/Models/Search/SearchFormActionTypeEnum.cs index 4fc86ce57..837a0e6bc 100644 --- a/LearningHub.Nhs.WebUI/Models/Search/SearchFormActionTypeEnum.cs +++ b/LearningHub.Nhs.Shared/Models/Search/SearchFormActionTypeEnum.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Models.Search +namespace LearningHub.Nhs.Shared.Models.Search { /// /// Defines the SearchFormActionTypeEnum. diff --git a/LearningHub.Nhs.WebUI/Models/Search/SearchRequestViewModel.cs b/LearningHub.Nhs.Shared/Models/Search/SearchRequestViewModel.cs similarity index 86% rename from LearningHub.Nhs.WebUI/Models/Search/SearchRequestViewModel.cs rename to LearningHub.Nhs.Shared/Models/Search/SearchRequestViewModel.cs index 8b4d5de04..f8b56cfd7 100644 --- a/LearningHub.Nhs.WebUI/Models/Search/SearchRequestViewModel.cs +++ b/LearningHub.Nhs.Shared/Models/Search/SearchRequestViewModel.cs @@ -1,8 +1,7 @@ -namespace LearningHub.Nhs.WebUI.Models.Search +namespace LearningHub.Nhs.Shared.Models.Search { using System.Collections.Generic; using System.ComponentModel.DataAnnotations; - using Microsoft.AspNetCore.Mvc; /// /// Defines the . @@ -13,67 +12,56 @@ public class SearchRequestViewModel /// Gets or sets the search string. /// [Required(ErrorMessage = "Search text is required")] - [FromQuery] public string Term { get; set; } /// /// Gets or sets the filters. /// - [FromQuery] public IEnumerable Filters { get; set; } /// /// Gets or sets the sort item index. /// - [FromQuery] public int? Sortby { get; set; } /// /// Gets or sets the catalogue current page index. /// - [FromQuery] public int? CataloguePageIndex { get; set; } /// /// Gets or sets the resource current page index. /// - [FromQuery] public int? ResourcePageIndex { get; set; } /// /// Gets or sets the group id. /// - [FromQuery] public string GroupId { get; set; } /// /// Gets or sets a value indicating whether gets or sets the feedback submitted. /// - [FromQuery] public bool? FeedbackSubmitted { get; set; } /// /// Gets or sets the search id. /// - [FromQuery] public int? SearchId { get; set; } /// /// Gets or sets the catalogue id, when searching within a particular catalogue. /// - [FromQuery] public int? CatalogueId { get; set; } /// /// Gets or sets the resource access level id. /// - [FromQuery] public int? ResourceAccessLevelId { get; set; } /// /// Gets or sets the provider ids. /// - [FromQuery] public IEnumerable ProviderFilters { get; set; } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Models/Search/SearchResultPagingModel.cs b/LearningHub.Nhs.Shared/Models/Search/SearchResultPagingModel.cs similarity index 92% rename from LearningHub.Nhs.WebUI/Models/Search/SearchResultPagingModel.cs rename to LearningHub.Nhs.Shared/Models/Search/SearchResultPagingModel.cs index 9082444d4..aa3954ef9 100644 --- a/LearningHub.Nhs.WebUI/Models/Search/SearchResultPagingModel.cs +++ b/LearningHub.Nhs.Shared/Models/Search/SearchResultPagingModel.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Models.Search +namespace LearningHub.Nhs.Shared.Models.Search { using LearningHub.Nhs.Models.Paging; diff --git a/LearningHub.Nhs.WebUI/Models/Search/SearchResultViewModel.cs b/LearningHub.Nhs.Shared/Models/Search/SearchResultViewModel.cs similarity index 98% rename from LearningHub.Nhs.WebUI/Models/Search/SearchResultViewModel.cs rename to LearningHub.Nhs.Shared/Models/Search/SearchResultViewModel.cs index 543a50e5f..a9d189677 100644 --- a/LearningHub.Nhs.WebUI/Models/Search/SearchResultViewModel.cs +++ b/LearningHub.Nhs.Shared/Models/Search/SearchResultViewModel.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Models.Search +namespace LearningHub.Nhs.Shared.Models.Search { using System; using LearningHub.Nhs.Models.Paging; diff --git a/LearningHub.Nhs.WebUI/Services/BaseService.cs b/LearningHub.Nhs.Shared/Services/BaseService.cs similarity index 77% rename from LearningHub.Nhs.WebUI/Services/BaseService.cs rename to LearningHub.Nhs.Shared/Services/BaseService.cs index ddcbad86f..68c74bc43 100644 --- a/LearningHub.Nhs.WebUI/Services/BaseService.cs +++ b/LearningHub.Nhs.Shared/Services/BaseService.cs @@ -1,6 +1,6 @@ -namespace LearningHub.Nhs.WebUI.Services +namespace LearningHub.Nhs.Shared.Services { - using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.Shared.Interfaces.Http; using Microsoft.Extensions.Logging; /// @@ -37,6 +37,20 @@ protected BaseService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttp this.logger = logger; } + + + /// + /// Initializes a new instance of the class. + /// + /// The openApiHttpClient. + /// The logger. + protected BaseService(IOpenApiHttpClient openApiHttpClient, ILogger logger) + { + this.openApiHttpClient = openApiHttpClient; + this.logger = logger; + } + + /// /// Gets the LearningHubHttpClient. /// diff --git a/LearningHub.Nhs.WebUI/Services/ProviderService.cs b/LearningHub.Nhs.Shared/Services/ProviderService.cs similarity index 97% rename from LearningHub.Nhs.WebUI/Services/ProviderService.cs rename to LearningHub.Nhs.Shared/Services/ProviderService.cs index ee6119c60..a4d56ed47 100644 --- a/LearningHub.Nhs.WebUI/Services/ProviderService.cs +++ b/LearningHub.Nhs.Shared/Services/ProviderService.cs @@ -1,4 +1,4 @@ -namespace LearningHub.Nhs.WebUI.Services +namespace LearningHub.Nhs.Shared.Services { using System; using System.Collections.Generic; @@ -6,7 +6,8 @@ using System.Threading.Tasks; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Provider; - using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Interfaces.Services; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/SearchService.cs b/LearningHub.Nhs.Shared/Services/SearchService.cs similarity index 95% rename from LearningHub.Nhs.WebUI/Services/SearchService.cs rename to LearningHub.Nhs.Shared/Services/SearchService.cs index 578a5861f..ab6dc87fb 100644 --- a/LearningHub.Nhs.WebUI/Services/SearchService.cs +++ b/LearningHub.Nhs.Shared/Services/SearchService.cs @@ -1,5 +1,19 @@ -namespace LearningHub.Nhs.WebUI.Services +namespace LearningHub.Nhs.Shared.Services { + using HtmlAgilityPack; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Search.SearchClick; + using LearningHub.Nhs.Shared.Configuration; + using LearningHub.Nhs.Shared.Helpers; + using LearningHub.Nhs.Shared.Interfaces.Configuration; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Interfaces.Services; + using LearningHub.Nhs.Shared.Models.Search; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -9,39 +23,30 @@ namespace LearningHub.Nhs.WebUI.Services using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; - using HtmlAgilityPack; - using LearningHub.Nhs.Models.Common; - using LearningHub.Nhs.Models.Enums; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Models.Search.SearchClick; - using LearningHub.Nhs.WebUI.Configuration; - using LearningHub.Nhs.WebUI.Helpers; - using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models.Search; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Newtonsoft.Json; /// /// Defines the . /// public class SearchService : BaseService, ISearchService { - private readonly Settings settings; + private readonly IExposableSettings ExposableSettings; private IProviderService providerService; /// /// Initializes a new instance of the class. /// - /// Learning hub http client. /// The Open Api Http Client. /// Provider service. /// Logger. - /// Settings. - public SearchService(ILearningHubHttpClient learningHubHttpClient, IOpenApiHttpClient openApiHttpClient, IProviderService providerService, ILogger logger, IOptions settings) - : base(learningHubHttpClient, openApiHttpClient, logger) + /// Settings. + public SearchService( + IOpenApiHttpClient openApiHttpClient, + IProviderService providerService, + ILogger logger, + IOptions ExposableSettings) + : base(openApiHttpClient, logger) { - this.settings = settings.Value; + this.ExposableSettings = ExposableSettings.Value; this.providerService = providerService; } @@ -68,8 +73,8 @@ public async Task PerformSearch(IPrincipal user, SearchRe var suggestedCatalogue = string.Empty; var suggestedResource = string.Empty; - var resourceSearchPageSize = this.settings.FindwiseSettings.ResourceSearchPageSize; - var catalogueSearchPageSize = this.settings.FindwiseSettings.CatalogueSearchPageSize; + var resourceSearchPageSize = this.ExposableSettings.FindwiseSettings.ResourceSearchPageSize; + var catalogueSearchPageSize = this.ExposableSettings.FindwiseSettings.CatalogueSearchPageSize; var resourceSearchRequestModel = new SearchRequestModel { @@ -164,10 +169,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 && MoodleHelper.FindwiseResourceMoodleTypeDict.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 = MoodleHelper.FindwiseResourceMoodleTypeDict[filter.DisplayName]; + var searchfilter = new SearchFilterModel() { DisplayName = MoodleHelper.GetPrettifiedResourceTypeNameMoodle(resourceTypeEnum), Count = filter.Count, Value = filteritem, Selected = searchRequest.Filters?.Contains(filter.DisplayName) ?? false }; searchfilters.Add(searchfilter); } } @@ -254,8 +259,8 @@ public async Task PerformSearch(IPrincipal user, SearchRe public async Task RegisterSearchEventsAsync(SearchRequestViewModel search, SearchFormActionTypeEnum action, int resourceCount = 0, int catalogueCount = 0) { var eventId = 0; - var resourceSearchPageSize = this.settings.FindwiseSettings.ResourceSearchPageSize; - var catalogueSearchPageSize = this.settings.FindwiseSettings.CatalogueSearchPageSize; + var resourceSearchPageSize = this.ExposableSettings.FindwiseSettings.ResourceSearchPageSize; + var catalogueSearchPageSize = this.ExposableSettings.FindwiseSettings.CatalogueSearchPageSize; var sortBy = search.Sortby.HasValue ? (SearchSortTypeEnum)search.Sortby : SearchSortTypeEnum.Relevance; @@ -512,7 +517,7 @@ public async Task SubmitFeedbackAsync(SearchFeedBackModel model) int createId = 0; var client = await this.OpenApiHttpClient.GetClientAsync(); - var request = $"Search/SubmitFeedback"; + var request = this.ExposableSettings.LearningHubApiUrl + "Search/SubmitFeedback"; var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); var response = await client.PostAsync(request, content).ConfigureAwait(false); diff --git a/LearningHub.Nhs.WebUI.BlazorClient/DI/DI.cs b/LearningHub.Nhs.WebUI.BlazorClient/DI/DI.cs new file mode 100644 index 000000000..3be1a1cb2 --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/DI/DI.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using LearningHub.Nhs.Shared.Configuration; + + +namespace LearningHub.Nhs.WebUI.BlazorClient.DI +{ + public static class DI + { + public static IHttpClientBuilder AddBffHttpClient(this IServiceCollection services, Func getApiUrl) + where TInterface : class + where TImplementation : class, TInterface + { + return services.AddHttpClient((serviceProvider, client) => + { + var ExposableSettings = serviceProvider.GetRequiredService>().Value; + var apiUrl = getApiUrl(ExposableSettings); + var apiUri = new Uri(apiUrl); + var apiHost = apiUri.Host; + string forwardSlash = "/"; + // Using the Uri class for robust path joining + client.BaseAddress = new Uri($"{ExposableSettings.LearningHubApiBFFUrl}{apiHost}{forwardSlash}"); + }); + } + } +} diff --git a/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj b/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj new file mode 100644 index 000000000..2082f00b7 --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj @@ -0,0 +1,56 @@ + + + + net8.0 + enable + enable + + + + true + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj.user b/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj.user new file mode 100644 index 000000000..877a5e481 --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/LearningHub.Nhs.WebUI.BlazorClient.csproj.user @@ -0,0 +1,9 @@ + + + + IIS Local + + + ProjectDebugger + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.BlazorClient/Program.cs b/LearningHub.Nhs.WebUI.BlazorClient/Program.cs new file mode 100644 index 000000000..d93936295 --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/Program.cs @@ -0,0 +1,111 @@ +//using LearningHub.Nhs.Models.Entities; +// Still required server side even if not used so components dont fail +using Blazored.LocalStorage; +using LearningHub.Nhs.WebUI.BlazorClient.DI; + +using LearningHub.Nhs.Caching; +using LearningHub.Nhs.Shared.Configuration; +using LearningHub.Nhs.Shared.Interfaces; +using LearningHub.Nhs.Shared.Interfaces.Configuration; +using LearningHub.Nhs.Shared.Interfaces.Http; +using LearningHub.Nhs.Shared.Interfaces.Services; +using LearningHub.Nhs.Shared.Services; +using LearningHub.Nhs.WebUI.BlazorClient.Services; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +// Serilog core (used via appsettings, do not delete even if vs marks not in use) +using Serilog; +using Serilog.Configuration; +using Serilog.Core; +using Serilog.Events; +// Serilog extensions and sinks (used via appsettings, do not delete even if vs marks not in use) +using Serilog.Extensions.Logging; +using Serilog.Formatting.Compact; +using Serilog.Settings.Configuration; +using Serilog.Sinks.BrowserConsole; +using System; +using TELBlazor.Components.Core.Configuration; +using TELBlazor.Components.Core.Services.HelperServices; +using TELBlazor.Components.OptionalImplementations.Core.Services.HelperServices; + + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +var http = new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }; +var env = builder.HostEnvironment.Environment; +using var envSettings = await http.GetStreamAsync($"appsettings.{env}.json"); + +builder.Configuration.AddJsonStream(envSettings); + +builder.Services.Configure(builder.Configuration.GetSection("Settings")); +builder.Logging.ClearProviders(); + +// Read default logging level from configuration +var logLevelString = builder.Configuration["Serilog:MinimumLevel:Default"]; +// Convert string to LogEventLevel (with fallback) +if (!Enum.TryParse(logLevelString, true, out LogEventLevel defaultLogLevel)) +{ + defaultLogLevel = LogEventLevel.Information; // Default if parsing fails +} + +// Create a LoggingLevelSwitch that can be updated dynamically +LoggingLevelSwitch levelSwitch = new LoggingLevelSwitch(defaultLogLevel); // Default: Information added this so in production can change the logging +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .MinimumLevel.ControlledBy(levelSwitch) + .CreateLogger(); + +// Add Serilog to logging providers +builder.Logging.AddSerilog(Log.Logger, dispose: true);//qqqq may not need dispose for client + +//for really bad fails +try +{ + // Candidates for DI collection + builder.Services.AddSingleton(sp => + { + return new TELBlazorBaseComponentConfiguration + { + JSEnabled = true, //if we are inject the client then it is true + HostType = $"{builder.Configuration["Properties:Environment"]} {builder.Configuration["Properties:Application"]}" + }; + }); + + builder.Services.AddBlazoredLocalStorage(); + + + // Register your BFF using httpclient ILearningHubHttpClient + builder.Services.AddBffHttpClient(settings => settings.LearningHubApiUrl); + + // Register your BFF using httpclient IUserApiHttpClient + builder.Services.AddBffHttpClient(settings => settings.UserApiUrl); + + // Register your BFF using httpclient IOpenApiHttpClient + builder.Services.AddBffHttpClient(settings => settings.OpenApiUrl); + + + builder.Services.AddScoped(sp => levelSwitch); + builder.Services.AddScoped(); + + builder.Services.AddScoped(); + + // qqqq will go in a shared DI service collection extension + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + await builder.Build().RunAsync(); +} +catch (Exception ex) +{ + //If in production as requires sending to api we may never receive it + Log.Fatal(ex, "Application terminated unexpectedly"); +} +finally +{ + Log.CloseAndFlush(); // Ensure logs are flushed before exit +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.BlazorClient/Services/GenericAPIHttpClient.cs b/LearningHub.Nhs.WebUI.BlazorClient/Services/GenericAPIHttpClient.cs new file mode 100644 index 000000000..f08a7c09c --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/Services/GenericAPIHttpClient.cs @@ -0,0 +1,30 @@ +using LearningHub.Nhs.Shared.Interfaces.Http; + +namespace LearningHub.Nhs.WebUI.BlazorClient.Services +{ + public class GenericAPIHttpClient : IAPIHttpClient, ILearningHubHttpClient, IUserApiHttpClient, IOpenApiHttpClient + { + private readonly HttpClient _httpClient; // Private field to hold the injected HttpClient + + /// + /// Initializes a new instance of the class. + /// + /// The HttpClient instance provided by dependency injection. + public GenericAPIHttpClient(HttpClient httpClient) // Inject HttpClient + { + _httpClient = httpClient; + } + + public string ApiUrl => _httpClient.BaseAddress.AbsoluteUri; + + /// + /// Retrieves the configured HttpClient instance. + /// + /// A Task that resolves to the HttpClient instance. + public Task GetClientAsync() + { + // Return the injected HttpClient instance wrapped in a completed Task + return Task.FromResult(_httpClient); + } + } +} diff --git a/LearningHub.Nhs.WebUI.BlazorClient/Services/WasmCacheServiceStub.cs b/LearningHub.Nhs.WebUI.BlazorClient/Services/WasmCacheServiceStub.cs new file mode 100644 index 000000000..3640c206c --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/Services/WasmCacheServiceStub.cs @@ -0,0 +1,62 @@ +using LearningHub.Nhs.Caching; + +namespace LearningHub.Nhs.WebUI.BlazorClient.Services +{ + /// + /// We may use storage, we may just stub it and throw an error, we cant directly use redis we may access it via an api + /// The cachestub currently just returns there is nothing available so the caller then will revert to calling the api. + /// + public class WasmCacheServiceStub : ICacheService + { + public Task GetAsync(string key) + { + return Task.FromResult(default(T)); + } + + public Task<(bool Success, T Value)> TryGetAsync(string key) + { + return Task.FromResult((false, default(T))); + } + + public Task SetAsync(string key, T value) + { + return Task.FromResult(value); + } + + public Task RemoveAsync(string key) + { + return Task.CompletedTask; + } + + public Task SetAsync(string key, T value, int? expiryInMinutes, bool slidingExpiration = true) + { + return Task.FromResult(value); + } + + public Task GetOrCreateAsync(string key, Func getValue) + { + return Task.FromResult(getValue()); + } + + public Task GetOrCreateAsync(string key, Func getValue, int? expiryInMinutes, bool slidingExpiration = true) + { + return Task.FromResult(getValue()); + } + + public Task GetOrFetchAsync(string key, Func> getValue) + { + return getValue(); + } + + public Task GetOrFetchAsync(string key, Func> getValue, int? expiryInMinutes, bool slidingExpiration = true) + { + return getValue(); + } + + public Task FlushAll() + { + return Task.CompletedTask; + } + + } +} diff --git a/LearningHub.Nhs.WebUI.BlazorClient/TestDeleteMe/APITestDeleteME.razor b/LearningHub.Nhs.WebUI.BlazorClient/TestDeleteMe/APITestDeleteME.razor new file mode 100644 index 000000000..39590e7eb --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/TestDeleteMe/APITestDeleteME.razor @@ -0,0 +1,66 @@ +@using LearningHub.Nhs.Shared.Interfaces.Http +@inject ILearningHubHttpClient LearningHubBFFHttpClient +@inherits TELBlazor.Components.Core.TELComponentBase +
+

⚠️ Works when required service packages moved to BlazorClient currently will fail on call, happens due to the runtime hack AllowUsingAspNetCoreInBlazorWasm ⚠️

+ + +
+
+ Response from custom endpoint: +
@APIResponse
+
+ +@code { + string ApiRoute = "Catalogue/GetLatestCatalogueAccessRequest/43"; // Default route 500 + string? APIResponse = "No response yet"; + + + protected override void OnInitialized() + { + base.OnInitialized(); // Call the base method + Logger.LogInformation("Serilogging"); + + + Logger.LogInformation( + "API Test Component initialized with default route: {Route}", + ApiRoute + ); + + Logger.LogInformation("!!!!!!!!!!!!!!!!!!!!!!!!! Base Component"); + } + + private async Task CallApi() + { + try + { + Logger.LogInformation("Base Component"); + var httpClient = await LearningHubBFFHttpClient.GetClientAsync(); + + Logger.LogInformation("📡 BaseAddress should be https://lh-web.dev.local/bff/lh-api.dev.local/ . HttpClient BaseAddress: {BaseAddress}", httpClient.BaseAddress?.ToString()); + Logger.LogInformation($"Logger: Calling via the bff this api route: {ApiRoute}"); + Logger.LogInformation("📡 Target: {TargetUrl}", "https://lh-web.dev.local/bff/lh-api.dev.local/Catalogue/GetLatestCatalogueAccessRequest/500"); + // Assuming httpClient.BaseAddress is set + string fullUrl = new Uri(httpClient.BaseAddress, ApiRoute).ToString(); + Logger.LogInformation("Making request to: {FullUrl}", fullUrl); + + // Now use standard HttpClient methods + var response = await httpClient.GetAsync(ApiRoute); + string responseContent = await response.Content.ReadAsStringAsync(); + + Logger.LogInformation("API Response - Status: {Status}, Content: {Content}", + response.StatusCode, responseContent); + + var content = await response.Content.ReadAsStringAsync(); + APIResponse = $"**Status:** {response.StatusCode}\n\n{content}"; + + /* qqqq + * // -> think need // https://lh-web.dev.local/bff/lh-api.dev.local/Catalogue/GetLatestCatalogueAccessRequest/500 + */ + } + catch (Exception ex) + { + Logger.LogError(ex, "An error occurred during API call to route: {Route}", ApiRoute); + } + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.BlazorClient/_Imports.razor b/LearningHub.Nhs.WebUI.BlazorClient/_Imports.razor new file mode 100644 index 000000000..1289a8aff --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using LearningHub.Nhs.WebUI.BlazorClient \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.BlazorClient/nuget.config.template b/LearningHub.Nhs.WebUI.BlazorClient/nuget.config.template new file mode 100644 index 000000000..dac496006 --- /dev/null +++ b/LearningHub.Nhs.WebUI.BlazorClient/nuget.config.template @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.Shared/LearningHub.Nhs.WebUI.Shared.csproj b/LearningHub.Nhs.WebUI.Shared/LearningHub.Nhs.WebUI.Shared.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/LearningHub.Nhs.WebUI.Shared/LearningHub.Nhs.WebUI.Shared.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/LearningHub.Nhs.WebUI.sln b/LearningHub.Nhs.WebUI.sln index 55cce01f0..24242191d 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -4,11 +4,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.WebUI", "LearningHub.Nhs.WebUI\LearningHub.Nhs.WebUI.csproj", "{16BBF937-C1E9-4240-B56E-20E3E5FA2005}" + ProjectSection(ProjectDependencies) = postProject + {9F1B0470-E809-49FE-A6E8-152C7EBD012E} = {9F1B0470-E809-49FE-A6E8-152C7EBD012E} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B5D48B6A-D4A7-494E-89C0-64428232D242}" ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props + .editorconfig = .editorconfig + global.json = global.json StyleCop.ruleset = StyleCop.ruleset EndProjectSection EndProject @@ -17,6 +20,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MigrationTool", "MigrationTool", "{94676CCE-A38B-4FAF-905E-CE85CE95845E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.Api", "WebAPI\LearningHub.Nhs.API\LearningHub.Nhs.Api.csproj", "{21F15E96-314F-4F39-822F-C2568CDC4A5A}" + ProjectSection(ProjectDependencies) = postProject + {9F1B0470-E809-49FE-A6E8-152C7EBD012E} = {9F1B0470-E809-49FE-A6E8-152C7EBD012E} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.Api.Shared", "WebAPI\LearningHub.Nhs.Api.Shared\LearningHub.Nhs.Api.Shared.csproj", "{719833B9-EC79-48F2-9123-C4DF111AE9AA}" EndProject @@ -51,6 +57,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdminUI", "AdminUI", "{9642BC19-BAE7-45A9-B4F2-8D7529786CDC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.AdminUI", "AdminUI\LearningHub.Nhs.AdminUI\LearningHub.Nhs.AdminUI.csproj", "{1C97A3C2-73E8-4AFF-92EF-F65B4899FADB}" + ProjectSection(ProjectDependencies) = postProject + {9F1B0470-E809-49FE-A6E8-152C7EBD012E} = {9F1B0470-E809-49FE-A6E8-152C7EBD012E} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenAPI", "OpenAPI", "{66ED23A2-F15A-4ECB-A84D-736C95BEFC61}" EndProject @@ -82,6 +91,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.ReportApi.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.WebUI.AutomatedUiTests", "LearningHub.Nhs.WebUI.AutomatedUiTests\LearningHub.Nhs.WebUI.AutomatedUiTests.csproj", "{A84EC50B-2B01-4819-A2B1-BD867B7595CA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.Shared", "LearningHub.Nhs.Shared\LearningHub.Nhs.Shared.csproj", "{9F1B0470-E809-49FE-A6E8-152C7EBD012E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.WebUI.BlazorClient", "LearningHub.Nhs.WebUI.BlazorClient\LearningHub.Nhs.WebUI.BlazorClient.csproj", "{A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.WebUI.Shared", "LearningHub.Nhs.WebUI.Shared\LearningHub.Nhs.WebUI.Shared.csproj", "{22596C8C-EF3A-4046-BB85-012D851D8D16}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -346,6 +361,30 @@ Global {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|Any CPU.Build.0 = Release|Any CPU {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|x64.ActiveCfg = Release|Any CPU {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|x64.Build.0 = Release|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Debug|x64.Build.0 = Debug|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Release|Any CPU.Build.0 = Release|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Release|x64.ActiveCfg = Release|Any CPU + {9F1B0470-E809-49FE-A6E8-152C7EBD012E}.Release|x64.Build.0 = Release|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Debug|x64.ActiveCfg = Debug|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Debug|x64.Build.0 = Debug|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Release|Any CPU.Build.0 = Release|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Release|x64.ActiveCfg = Release|Any CPU + {A7DA82FE-A46C-47E9-8BD6-7FD7A8376CBA}.Release|x64.Build.0 = Release|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Debug|x64.ActiveCfg = Debug|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Debug|x64.Build.0 = Debug|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Release|Any CPU.Build.0 = Release|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Release|x64.ActiveCfg = Release|Any CPU + {22596C8C-EF3A-4046-BB85-012D851D8D16}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LearningHub.Nhs.WebUI/BlazorPageHosting/App.razor b/LearningHub.Nhs.WebUI/BlazorPageHosting/App.razor new file mode 100644 index 000000000..884020c95 --- /dev/null +++ b/LearningHub.Nhs.WebUI/BlazorPageHosting/App.razor @@ -0,0 +1 @@ +@* No-op App component *@ diff --git a/LearningHub.Nhs.WebUI/Configuration/BFFPathValidationOptions.cs b/LearningHub.Nhs.WebUI/Configuration/BFFPathValidationOptions.cs new file mode 100644 index 000000000..3d0d490af --- /dev/null +++ b/LearningHub.Nhs.WebUI/Configuration/BFFPathValidationOptions.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.WebUI.Configuration +{ + using System.Collections.Generic; + + /// + /// Configuration options for validating BFF paths. + /// + public class BFFPathValidationOptions + { + /// + /// Gets the section name for BFF path validation options. + /// + public const string SectionName = "BFFPathValidation"; + + /// + /// Gets or sets which apis and api stems we are allowing. + /// + public List AllowedPathPrefixes { get; set; } = new List(); + + /// + /// Gets or sets fine tuning of what paths the BFF can be used to access and what not to, where we want to specifically protect against something. + /// + public List BlockedPathSegments { get; set; } = new List(); + } +} diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index e300e80e3..b8a26f73b 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -1,12 +1,14 @@ namespace LearningHub.Nhs.WebUI.Configuration { using System; + using LearningHub.Nhs.Shared.Configuration; + using LearningHub.Nhs.Shared.Interfaces.Configuration; using LearningHub.Nhs.WebUI.Models.Contribute; /// /// Defines the . /// - public class Settings + public class Settings : IExposableSettings { /// /// Initializes a new instance of the class. @@ -31,6 +33,9 @@ public Settings() /// public string LearningHubApiUrl { get; set; } + /// + public string LearningHubApiBFFUrl { get; set; } + /// /// Gets or sets the OpenApiUrl. /// @@ -254,7 +259,7 @@ public Settings() /// /// Gets or sets the FindwiseSettings. /// - public FindwiseSettings FindwiseSettings { get; set; } = new FindwiseSettings(); + public IExposableFindwiseSettings FindwiseSettings { get; set; } = new ExposableFindwiseSettings(); /// /// Gets or sets the MediaKindSettings. diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/BFFController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/BFFController.cs new file mode 100644 index 000000000..ef3cf02de --- /dev/null +++ b/LearningHub.Nhs.WebUI/Controllers/Api/BFFController.cs @@ -0,0 +1,247 @@ +namespace LearningHub.Nhs.WebUI.Controllers.Api +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.WebUI.Configuration; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + + /// + /// This controller allows proxying of requests to different APIs using same site cookie authentication. + /// It uses the http clients registered in the DI container. + /// The BFF (Backend for Frontend) pattern is used to simplify client-side code and centralize API access, application services directly call external apis currently but they could use the bff and then introduce caching there too potentially for seperation of infastructure concerns. + /// Unauthorized requests will be redirected to the login page so 302s are expected when unauthorized, and redirecting for using a Blazor island component for example may not be desireable so these responses need to be handled by the caller. + /// This controller is designed to be used with a clientside calls i.e. Blazor utilizing the BFF pattern, which enables same site cookie authentication and avoid the necessity of storing tokens in client storage + /// The bff prefix is followed by the API name (e.g. "learninghub", "userapi") and the path to the specific endpoint to enable easy routing to different APIs. + /// See confluence for more details on the BFF pattern and how to use this controller. + /// + /// The authorize same site cookie is used for security between client and server. API calls relying on policys such as AuthorizeOrCallFromLH may not be proxied as they require the Authorization header to be present. + [Authorize] + [Route("bff/{apiName}/{**path}")] + [ApiController] + public class BFFController : BaseApiController + { + private readonly IOptions bffPathValidationOptions; + + /// + /// The list of API clients that can be used to proxy requests. + /// + private List apiClients; + + /// + /// Initializes a new instance of the class. + /// + /// The logger instance used for logging. + /// The HTTP client for the Learning Hub API. + /// The HTTP client for the User API. + /// The HTTP client for the Open API. + /// The options for validating BFF paths. + public BFFController( + ILogger logger, + ILearningHubHttpClient learningHubClient, + IUserApiHttpClient userAPIClient, + IOpenApiHttpClient openAPIClient, + IOptions bffPathValidationOptions) + : base(logger) + { + // Clients the BFF is being given access to, these are the only clients that can be used to proxy requests. + this.apiClients = new List() + { + learningHubClient, + userAPIClient, + openAPIClient, + }; + + this.bffPathValidationOptions = bffPathValidationOptions; + } + + /// + /// Takes an API name and a path, and proxies the request to the appropriate API provided that api is part of the client list and the path is allowed and not blocked. + /// + /// The name of the API to which the request should be proxied. + /// The path of the endpoint within the specified API. + /// An representing the result of the proxied request. + [HttpGet] + [HttpPost] + [HttpPut] + [HttpDelete] + [HttpPatch] + public async Task ProxyRequest(string apiName, string path) + { + string sanitizedPath = path?.Trim('/').ToLowerInvariant() ?? string.Empty; + string sanitizedApiName = apiName?.Trim('/').ToLowerInvariant() ?? string.Empty; + + // qqqq https://lh-web.dev.local/bff/lh-api.dev.local/Catalogue/GetLatestCatalogueAccessRequest/500 + IAPIHttpClient apiClient; + try + { + apiClient = this.apiClients.Single(x => + { + try + { + var uri = new Uri(x.ApiUrl); + return uri.Host.ToLowerInvariant() == sanitizedApiName; + } + catch + { + return false; + } + }); + } + catch (Exception e) + { + this.Logger.LogError(e, "Failed to find API client for {ApiName}", sanitizedApiName); + return this.BadRequest($"Unknown API alias: {sanitizedApiName}"); + } + + if (!this.IsPathAllowed(sanitizedPath)) + { + // qqqq "catalogue/getlatestcatalogueaccessrequest/500" + return this.Forbid("This path is not allowed via BFF proxy."); + } + + var client = await apiClient.GetClientAsync(); + string targetUrl = $"{apiClient.ApiUrl.TrimEnd('/')}/{path}"; + + // Add query parameters from the original request + if (this.Request.QueryString.HasValue) + { + targetUrl += this.Request.QueryString.Value; + } + + /* + No headers for Auth, host, connection, user agent, added becaue all security is handled by serverside httpclients via baseclient + BaseHttpClient should handle content-type, timezone and tokens. + Note: We do not forward the Authorization header as the BFF pattern uses same-site cookies for authentication. + This means the BFF controller is responsible for handling authentication and authorization. + We also do not forward the Host header as it may not match the target API's expected host. + Header copying would only be needed if: APIs start checking for custom client headers (X-Custom-Header, X-Correlation-Id, etc.) + */ + + // Copy body if necessary (for POST, PUT, PATCH, etc.) + var method = new HttpMethod(this.Request.Method); + var requestMessage = new HttpRequestMessage(method, targetUrl); + + if (this.Request.ContentLength > 0 && + !string.Equals(this.Request.Method, "GET", StringComparison.OrdinalIgnoreCase) && + !string.Equals(this.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase)) + { + requestMessage.Content = new StreamContent(this.Request.Body); + if (!string.IsNullOrEmpty(this.Request.ContentType)) + { + requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(this.Request.ContentType); + } + } + + try + { + var response = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead); + + // Handle redirects with token preservation + if (response.StatusCode == System.Net.HttpStatusCode.Redirect || + response.StatusCode == System.Net.HttpStatusCode.Found || + response.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect || + response.StatusCode == System.Net.HttpStatusCode.PermanentRedirect) + { + return await this.HandleRedirect(response, apiClient); + } + + var content = await response.Content.ReadAsStringAsync(); + var contentType = response.Content.Headers.ContentType?.MediaType ?? "application/json"; + + return new ContentResult + { + Content = content, + ContentType = contentType, + StatusCode = (int)response.StatusCode, + }; + } + catch (HttpRequestException ex) + { + this.Logger.LogError(ex, "Error proxying request to {TargetUrl}", targetUrl); + return this.StatusCode(500, "An error occurred while processing the request."); + } + } + + /* + Handle redirects with token preservation + if we are redirected the client may not handle it as it isnt the token holder so we need to continue using the bff until we get the outcome + if the BFF caller is not expecting redirects but only data they should handle the 302 response and redirect themselves. + E.g. A compontent that uses the BFF to fetch data may not be appropriate for redirecting to a specific page so the consuming client may need to have a way of handling page redirects. + */ + private async Task HandleRedirect(HttpResponseMessage response, IAPIHttpClient apiClient) + { + var location = response.Headers.Location?.ToString(); + + if (string.IsNullOrEmpty(location)) + { + return this.StatusCode((int)response.StatusCode, "Redirect location not found"); + } + + // Check if the redirect location is relative or absolute + string redirectUrl; + if (Uri.IsWellFormedUriString(location, UriKind.Absolute)) + { + redirectUrl = location; + } + else + { + // Handle relative redirects + var baseUri = new Uri(apiClient.ApiUrl); + redirectUrl = new Uri(baseUri, location).ToString(); + } + + // Create a new request for the redirect + var redirectRequest = new HttpRequestMessage(HttpMethod.Get, redirectUrl); + + // Add authentication token to the redirect request (apiClient handles this) + // No additional headers needed - apiClient is already configured + try + { + var client = await apiClient.GetClientAsync(); + var redirectResponse = await client.SendAsync(redirectRequest); + var content = await redirectResponse.Content.ReadAsStringAsync(); + + // Our data apis are expected to return JSON, but we can handle other content types if necessary. + var contentType = redirectResponse.Content.Headers.ContentType?.MediaType ?? "application/json"; + + return new ContentResult + { + Content = content, + ContentType = contentType, + StatusCode = (int)redirectResponse.StatusCode, + }; + } + catch (HttpRequestException ex) + { + this.Logger.LogError(ex, "Error following redirect to {RedirectUrl}", redirectUrl); + return this.StatusCode(500, "An error occurred while following the redirect."); + } + } + + /// + /// Validates the path against allowed and blocked segments. + /// + private bool IsPathAllowed(string path) + { + var normalizedPath = path?.Trim('/').ToLowerInvariant() ?? string.Empty; + + // Check blacklist first + if (this.bffPathValidationOptions.Value.BlockedPathSegments.Any(blocked => normalizedPath.Contains(blocked.ToLowerInvariant()))) + { + this.Logger.LogError(" Black listed path {path} was requested and blocked", normalizedPath); + return false; + } + + // Check whitelist + return this.bffPathValidationOptions.Value.AllowedPathPrefixes.Any(prefix => normalizedPath.StartsWith(prefix.ToLowerInvariant())); + } + } +} diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ProviderController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ProviderController.cs index ae3ccb97d..a154d4da8 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ProviderController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ProviderController.cs @@ -1,6 +1,7 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System.Threading.Tasks; + using LearningHub.Nhs.Shared.Interfaces.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/SearchController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/SearchController.cs index 1c7eef217..a83e2234d 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/SearchController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/SearchController.cs @@ -5,6 +5,11 @@ using System.Threading.Tasks; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Shared.Helpers; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Interfaces.Services; + using LearningHub.Nhs.Shared.Models.Search; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 12d9973ac..ddb34dc6f 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -13,11 +13,13 @@ using LearningHub.Nhs.Models.Hierarchy; using LearningHub.Nhs.Models.Search; using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Shared.Interfaces.Services; + using LearningHub.Nhs.Shared.Models; + using LearningHub.Nhs.Shared.Models.Search; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models.Catalogue; - using LearningHub.Nhs.WebUI.Models.Search; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; @@ -270,7 +272,7 @@ public async Task IndexAsync(string reference, string tab, int? n { if (viewModel.SearchResults == null) { - viewModel.SearchResults = new Models.Search.SearchResultViewModel(); + viewModel.SearchResults = new LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel(); } if (search.Term != null) diff --git a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs index fc3b7f76a..27ae615ce 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"); } 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/Controllers/LogoutController.cs b/LearningHub.Nhs.WebUI/Controllers/LogoutController.cs index 99244e3ed..9d79d1f24 100644 --- a/LearningHub.Nhs.WebUI/Controllers/LogoutController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/LogoutController.cs @@ -6,14 +6,11 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; - using IdentityModel; using IdentityModel.Client; - + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Handlers; - using LearningHub.Nhs.WebUI.Interfaces; - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; diff --git a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs index a7b53fb11..1438a7ba4 100644 --- a/LearningHub.Nhs.WebUI/Controllers/SearchController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/SearchController.cs @@ -7,9 +7,11 @@ namespace LearningHub.Nhs.WebUI.Controllers using System.Threading.Tasks; using LearningHub.Nhs.Models.Search; using LearningHub.Nhs.Models.Search.SearchClick; + using LearningHub.Nhs.Shared.Interfaces.Services; + using LearningHub.Nhs.Shared.Models.Search; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models.Search; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; @@ -60,7 +62,7 @@ public SearchController( /// filter applied. /// The actionResult. [HttpGet("results")] - public async Task Index(SearchRequestViewModel search, bool noSortFilterError = false, bool emptyFeedbackError = false, bool filterApplied = false) + public async Task Index([FromQuery] SearchRequestViewModel search, bool noSortFilterError = false, bool emptyFeedbackError = false, bool filterApplied = false) { search.SearchId ??= 0; search.GroupId = !string.IsNullOrWhiteSpace(search.GroupId) && Guid.TryParse(search.GroupId, out Guid groupId) ? groupId.ToString() : Guid.NewGuid().ToString(); diff --git a/LearningHub.Nhs.WebUI/Helpers/LearningHubApiFacade.cs b/LearningHub.Nhs.WebUI/Helpers/LearningHubApiFacade.cs index adeb102a7..204333bba 100644 --- a/LearningHub.Nhs.WebUI/Helpers/LearningHubApiFacade.cs +++ b/LearningHub.Nhs.WebUI/Helpers/LearningHubApiFacade.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Common; - using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.Shared.Interfaces.Http; using Newtonsoft.Json; /// diff --git a/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs b/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs index 32c529a0d..3627bc4a8 100644 --- a/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs +++ b/LearningHub.Nhs.WebUI/Helpers/OpenApiFacade.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Common; - using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.Shared.Interfaces.Http; using Newtonsoft.Json; /// diff --git a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index ef71191f9..515786d09 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -8,6 +8,7 @@ using HtmlAgilityPack; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Shared.Helpers; using Microsoft.AspNetCore.Mvc.Rendering; /// @@ -32,25 +33,6 @@ public static class UtilityHelper { "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 }, - }; - /// /// The FormatTwitterDate. /// @@ -138,7 +120,7 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType case ResourceTypeEnum.Article: return "Article"; case ResourceTypeEnum.Audio: - string durationText = GetDurationText(durationInMilliseconds ?? 0); + string durationText = FormattingHelper.GetDurationText(durationInMilliseconds ?? 0); durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; return "Audio" + durationText; case ResourceTypeEnum.Equipment: @@ -148,7 +130,7 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType case ResourceTypeEnum.Scorm: return "elearning"; case ResourceTypeEnum.Video: - durationText = GetDurationText(durationInMilliseconds ?? 0); + durationText = FormattingHelper.GetDurationText(durationInMilliseconds ?? 0); durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; return "Video" + durationText; case ResourceTypeEnum.WebLink: @@ -166,141 +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. - /// - /// The resource type. - /// The resource type name, and duration if applicable. - public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType) - { - switch (resourceType) - { - case ResourceTypeEnum.Assessment: - return "Assessment"; - case ResourceTypeEnum.Article: - return "Article"; - case ResourceTypeEnum.Audio: - return "Audio"; - case ResourceTypeEnum.Equipment: - return "Equipment"; - case ResourceTypeEnum.Image: - return "Image"; - case ResourceTypeEnum.Scorm: - return "elearning"; - case ResourceTypeEnum.Video: - return "Video"; - case ResourceTypeEnum.WebLink: - return "Web link"; - case ResourceTypeEnum.GenericFile: - return "File"; - case ResourceTypeEnum.Embedded: - return "Embedded"; - case ResourceTypeEnum.Case: - return "Case"; - case ResourceTypeEnum.Html: - return "HTML"; - default: - return "File"; - } - } - - /// - /// Returns a number of milliseconds converted into a duration string, such as "10 min 15 sec". Includes rounding to match the behaviour of the Azure Media Player. - /// - /// The number of milliseconds. - /// The duration string. - public static string GetDurationText(int durationInMilliseconds) - { - if (durationInMilliseconds > 0) - { - // Azure media player rounds duration to nearest second. e.g. 8:59.88 becomes 9:00. LH needs to match. - int nearestSecond = (int)Math.Round(((double)durationInMilliseconds) / 1000); - var duration = new TimeSpan(0, 0, nearestSecond); - string returnValue = string.Empty; - - // If duration greater than an hour, don't return the seconds part. - if (duration.Hours > 0) - { - returnValue = $"{duration.Hours} hr {duration.Minutes} min "; - - // Exclude "0 min" from the return value. - if (returnValue.EndsWith(" 0 min ")) - { - returnValue = returnValue.Replace("0 min ", string.Empty); - } - } - else - { - returnValue = $"{duration.Minutes} min {duration.Seconds} sec "; - - // Exclude "0 min" and "0 sec" from the return value. - if (returnValue.StartsWith("0 min ")) - { - returnValue = returnValue.Replace("0 min ", string.Empty); - } - - if (returnValue.EndsWith(" 0 sec ")) - { - returnValue = returnValue.Replace("0 sec ", string.Empty); - } - } - - return returnValue; - } - else - { - return string.Empty; - } - } - /// /// Returns a string containing either the authoredBy or organisation string, or a combination of both if present. /// 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/ILearningHubHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/ILearningHubHttpClient.cs deleted file mode 100644 index d39113d99..000000000 --- a/LearningHub.Nhs.WebUI/Interfaces/ILearningHubHttpClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Interfaces -{ - using System.Net.Http; - using System.Threading.Tasks; - - /// - /// The LearningHubHttpClient interface. - /// - public interface ILearningHubHttpClient - { - /// - /// The get client. - /// - /// The . - Task GetClientAsync(); - } -} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs b/LearningHub.Nhs.WebUI/Interfaces/IMoodleApiService.cs index d92c01fad..60dc18cb6 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/Interfaces/IOpenApiHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs deleted file mode 100644 index 4c53a2c38..000000000 --- a/LearningHub.Nhs.WebUI/Interfaces/IOpenApiHttpClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Interfaces -{ - using System.Net.Http; - using System.Threading.Tasks; - - /// - /// The OpenApiHttpClient interface. - /// - public interface IOpenApiHttpClient - { - /// - /// The get client. - /// - /// The . - Task GetClientAsync(); - } -} diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserApiHttpClient.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserApiHttpClient.cs deleted file mode 100644 index fb65ab6ee..000000000 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserApiHttpClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Interfaces -{ - using System.Net.Http; - using System.Threading.Tasks; - - /// - /// The User Api HttpClient interface. - /// - public interface IUserApiHttpClient - { - /// - /// The get client. - /// - /// The . - Task GetClientAsync(); - } -} diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index dc2cf661d..ed93048b4 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -97,6 +97,9 @@ + + + @@ -104,6 +107,10 @@ + + + + @@ -112,8 +119,8 @@ - - + + @@ -167,6 +174,12 @@ + + + + + + diff --git a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs index b9fd30dca..c5835ab94 100644 --- a/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/Catalogue/CatalogueIndexViewModel.cs @@ -4,7 +4,7 @@ using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Hierarchy; using LearningHub.Nhs.Models.User; - using LearningHub.Nhs.WebUI.Models.Search; + using LearningHub.Nhs.Shared.Models.Search; /// /// Defines the . 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/Program.cs b/LearningHub.Nhs.WebUI/Program.cs index c24d9057c..34013d03a 100644 --- a/LearningHub.Nhs.WebUI/Program.cs +++ b/LearningHub.Nhs.WebUI/Program.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using LearningHub.Nhs.WebUI; +using LearningHub.Nhs.WebUI.BlazorPageHosting; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.JsDetection; using LearningHub.Nhs.WebUI.Middleware; @@ -18,7 +19,6 @@ using tusdotnet; using tusdotnet.Models; using tusdotnet.Models.Configuration; - #pragma warning restore SA1200 // Using directives should be placed correctly var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger(); @@ -43,10 +43,13 @@ var appLifetime = app.Services.GetRequiredService(); var jsDetectionLogger = app.Services.GetRequiredService(); appLifetime.ApplicationStopping.Register(async () => await jsDetectionLogger.FlushCounters()); + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); + app.UseWebAssemblyDebugging(); } else { @@ -84,7 +87,6 @@ app.UseAuthorization(); app.UseMiddleware(); - app.UseStaticFiles(); app.Map(TimezoneInfoMiddleware.TimezoneInfoUrl, b => b.UseMiddleware()); @@ -108,6 +110,12 @@ }; }); + app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(LearningHub.Nhs.WebUI.BlazorClient._Imports).Assembly) + .AddAdditionalAssemblies(typeof(TELBlazor.Components._Imports).Assembly); + app.Run(); } catch (Exception ex) diff --git a/LearningHub.Nhs.WebUI/Services/ActivityService.cs b/LearningHub.Nhs.WebUI/Services/ActivityService.cs index 185dc3a2c..963c246ac 100644 --- a/LearningHub.Nhs.WebUI/Services/ActivityService.cs +++ b/LearningHub.Nhs.WebUI/Services/ActivityService.cs @@ -8,6 +8,8 @@ using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Resource.Activity; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/BaseHttpClient.cs b/LearningHub.Nhs.WebUI/Services/BaseHttpClient.cs index a8ebda5ef..cb2900fc2 100644 --- a/LearningHub.Nhs.WebUI/Services/BaseHttpClient.cs +++ b/LearningHub.Nhs.WebUI/Services/BaseHttpClient.cs @@ -10,6 +10,7 @@ using IdentityModel.Client; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Extensions; using Microsoft.AspNetCore.Authentication; @@ -20,7 +21,7 @@ /// /// The abstract api http client. /// - public abstract class BaseHttpClient + public abstract class BaseHttpClient : IAPIHttpClient { private static readonly ConcurrentDictionary DictionaryLocks = new ConcurrentDictionary(); diff --git a/LearningHub.Nhs.WebUI/Services/BoomarkService.cs b/LearningHub.Nhs.WebUI/Services/BoomarkService.cs index 15d5509f9..e18d467a1 100644 --- a/LearningHub.Nhs.WebUI/Services/BoomarkService.cs +++ b/LearningHub.Nhs.WebUI/Services/BoomarkService.cs @@ -6,6 +6,8 @@ using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Bookmark; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/CardService.cs b/LearningHub.Nhs.WebUI/Services/CardService.cs index d3aac0616..f641a921c 100644 --- a/LearningHub.Nhs.WebUI/Services/CardService.cs +++ b/LearningHub.Nhs.WebUI/Services/CardService.cs @@ -6,6 +6,8 @@ namespace LearningHub.Nhs.WebUI.Services using System.Text; using System.Threading.Tasks; using LearningHub.Nhs.Models.Resource.ResourceDisplay; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs index 3833a4e28..08885adc1 100644 --- a/LearningHub.Nhs.WebUI/Services/CatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Services/CatalogueService.cs @@ -10,6 +10,8 @@ using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.User; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/ContentService.cs b/LearningHub.Nhs.WebUI/Services/ContentService.cs index b6d1f9296..b46d4ae4d 100644 --- a/LearningHub.Nhs.WebUI/Services/ContentService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContentService.cs @@ -1,8 +1,10 @@ -namespace LearningHub.Nhs.WebUI.Services +namespace LearningHub.Nhs.Shared.Services { using System; using System.Threading.Tasks; using LearningHub.Nhs.Models.Content; + using LearningHub.Nhs.Shared.Interfaces; + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/ContributeService.cs b/LearningHub.Nhs.WebUI/Services/ContributeService.cs index 68a2b4cf6..ec5e73894 100644 --- a/LearningHub.Nhs.WebUI/Services/ContributeService.cs +++ b/LearningHub.Nhs.WebUI/Services/ContributeService.cs @@ -13,6 +13,8 @@ using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models.Contribute; using Microsoft.AspNetCore.Http; diff --git a/LearningHub.Nhs.WebUI/Services/CountryService.cs b/LearningHub.Nhs.WebUI/Services/CountryService.cs index e1afd4b35..ccbe6536a 100644 --- a/LearningHub.Nhs.WebUI/Services/CountryService.cs +++ b/LearningHub.Nhs.WebUI/Services/CountryService.cs @@ -5,6 +5,8 @@ using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; using elfhHub.Nhs.Models.Entities; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/DashboardService.cs b/LearningHub.Nhs.WebUI/Services/DashboardService.cs index 607d4f2be..abf22e7be 100644 --- a/LearningHub.Nhs.WebUI/Services/DashboardService.cs +++ b/LearningHub.Nhs.WebUI/Services/DashboardService.cs @@ -9,7 +9,10 @@ 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.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; @@ -128,14 +131,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/DetectJsLogService.cs b/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs index e9bf607ce..cbee0894d 100644 --- a/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs +++ b/LearningHub.Nhs.WebUI/Services/DetectJsLogService.cs @@ -2,6 +2,8 @@ { using System; using System.Threading.Tasks; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; diff --git a/LearningHub.Nhs.WebUI/Services/GradeService.cs b/LearningHub.Nhs.WebUI/Services/GradeService.cs index 37df9b048..0394b9543 100644 --- a/LearningHub.Nhs.WebUI/Services/GradeService.cs +++ b/LearningHub.Nhs.WebUI/Services/GradeService.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/HierarchyService.cs b/LearningHub.Nhs.WebUI/Services/HierarchyService.cs index 6bed5fd27..d68e29447 100644 --- a/LearningHub.Nhs.WebUI/Services/HierarchyService.cs +++ b/LearningHub.Nhs.WebUI/Services/HierarchyService.cs @@ -4,6 +4,8 @@ using System.Threading.Tasks; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; diff --git a/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs b/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs index bb19a0f38..67fcb25ea 100644 --- a/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs +++ b/LearningHub.Nhs.WebUI/Services/InternalSystemService.cs @@ -3,6 +3,8 @@ using System; using System.Threading.Tasks; using LearningHub.Nhs.Models.Maintenance; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/JobRoleService.cs b/LearningHub.Nhs.WebUI/Services/JobRoleService.cs index 766d05e60..7b9c3a674 100644 --- a/LearningHub.Nhs.WebUI/Services/JobRoleService.cs +++ b/LearningHub.Nhs.WebUI/Services/JobRoleService.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models.Account; diff --git a/LearningHub.Nhs.WebUI/Services/LearningHubHttpClient.cs b/LearningHub.Nhs.WebUI/Services/LearningHubHttpClient.cs index 460ddabc3..9af95a1e6 100644 --- a/LearningHub.Nhs.WebUI/Services/LearningHubHttpClient.cs +++ b/LearningHub.Nhs.WebUI/Services/LearningHubHttpClient.cs @@ -2,6 +2,7 @@ { using System.Net.Http; using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Http; diff --git a/LearningHub.Nhs.WebUI/Services/LocationService.cs b/LearningHub.Nhs.WebUI/Services/LocationService.cs index 04423a0b6..578532520 100644 --- a/LearningHub.Nhs.WebUI/Services/LocationService.cs +++ b/LearningHub.Nhs.WebUI/Services/LocationService.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs b/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs index 06fd7e428..c06f530c4 100644 --- a/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs +++ b/LearningHub.Nhs.WebUI/Services/LoginWizardService.cs @@ -9,6 +9,8 @@ using elfhHub.Nhs.Models.Entities; using elfhHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs index 068219b42..09bd89a8e 100644 --- a/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs +++ b/LearningHub.Nhs.WebUI/Services/MoodleApiService.cs @@ -3,15 +3,16 @@ 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.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Logging; using Newtonsoft.Json; + using MoodleCourseCompletionModel = LearningHub.Nhs.Models.Moodle.API.MoodleCourseCompletionModel; /// /// MoodleApiService. @@ -19,14 +20,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 +74,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 +96,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 +123,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 +144,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/Services/MyLearningService.cs b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs index 1e1662201..7aa1d65ad 100644 --- a/LearningHub.Nhs.WebUI/Services/MyLearningService.cs +++ b/LearningHub.Nhs.WebUI/Services/MyLearningService.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/NLogLogLevelSwitcherService.cs b/LearningHub.Nhs.WebUI/Services/NLogLogLevelSwitcherService.cs new file mode 100644 index 000000000..bae626eb1 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Services/NLogLogLevelSwitcherService.cs @@ -0,0 +1,97 @@ +namespace LearningHub.Nhs.WebUI.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Blazored.LocalStorage; + using Microsoft.Extensions.Logging; + using TELBlazor.Components.Core.Models.Logging; + using TELBlazor.Components.Core.Services.HelperServices; + + /// + /// Provides functionality for managing log levels in applications using NLog. + /// + /// This service implements the interface to provide log + /// level management. Note that NLog does not support runtime log level switching in WASM environments. As a result, + /// methods in this service primarily serve to fulfill the interface contract and provide default + /// behaviors. + public class NLogLogLevelSwitcherService : ILogLevelSwitcherService + { + private const string LogLevelKey = "logLevel"; + private readonly ILogger logger; + private readonly ILocalStorageService localStorage; + + /// + /// Initializes a new instance of the class. + /// + /// The local storage service. + /// The logger instance. + public NLogLogLevelSwitcherService(ILocalStorageService localStorage, ILogger logger) + { + this.localStorage = localStorage; + this.logger = logger; + } + + /// + public bool IsInitialized { get; set; } = false; + + /// + public async Task InitializeLogLevelFromAsyncSourceIfAvailable() + { + // NLog does not support runtime level switching in WASM + // Here only to mirror the interface + this.logger.LogInformation("NLog does not support dynamic runtime log level switching."); + await Task.CompletedTask; + } + + /// + public List GetAvailableLogLevels() => + Enum.GetNames(typeof(LogLevel)).ToList(); + + /// + public string GetCurrentLogLevel() + { + this.logger.LogInformation("Returning default log level (NLog does not support querying runtime level)."); + return "Information"; + } + + /// + public string SetLogLevel(string level) + { + this.logger.LogInformation("Requested to change log level to {Level}, but NLog does not support runtime changes in WASM.", level); + this.LogAllLevels("After 'Change'"); + + _ = this.StoreLogLevelWithTimestamp(level); // Fire and forget + return this.GetCurrentLogLevel(); + } + + private void LogAllLevels(string phase) + { + this.logger.LogTrace("[{Phase}] TRACE log", phase); + this.logger.LogDebug("[{Phase}] DEBUG log", phase); + this.logger.LogInformation("[{Phase}] INFO log", phase); + this.logger.LogWarning("[{Phase}] WARN log", phase); + this.logger.LogError("[{Phase}] ERROR log", phase); + this.logger.LogCritical("[{Phase}] CRITICAL log", phase); + } + + private async Task StoreLogLevelWithTimestamp(string level) + { + try + { + var newItem = new LocalStorageLogLevel + { + Level = level, + Expires = DateTime.UtcNow.AddHours(24), + }; + + await this.localStorage.SetItemAsync(LogLevelKey, newItem); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Error storing log level to local storage."); + } + } + } +} diff --git a/LearningHub.Nhs.WebUI/Services/NotificationService.cs b/LearningHub.Nhs.WebUI/Services/NotificationService.cs index e836edcd9..14eaaf300 100644 --- a/LearningHub.Nhs.WebUI/Services/NotificationService.cs +++ b/LearningHub.Nhs.WebUI/Services/NotificationService.cs @@ -9,6 +9,8 @@ using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Notification; using LearningHub.Nhs.Models.Paging; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs b/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs index 54c3b7bb2..502025b0b 100644 --- a/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs +++ b/LearningHub.Nhs.WebUI/Services/OpenApiHttpClient.cs @@ -2,8 +2,8 @@ { using System.Net.Http; using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Configuration; - using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs b/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs index 5ef45d685..b97309e65 100644 --- a/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs +++ b/LearningHub.Nhs.WebUI/Services/PartialFileUploadService.cs @@ -12,6 +12,8 @@ using Azure.Storage.Files.Shares; using Azure.Storage.Files.Shares.Models; using LearningHub.Nhs.Models.Resource.Files; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.StaticFiles; diff --git a/LearningHub.Nhs.WebUI/Services/RatingService.cs b/LearningHub.Nhs.WebUI/Services/RatingService.cs index fa72f4264..eedb5cf67 100644 --- a/LearningHub.Nhs.WebUI/Services/RatingService.cs +++ b/LearningHub.Nhs.WebUI/Services/RatingService.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/RegionService.cs b/LearningHub.Nhs.WebUI/Services/RegionService.cs index f05fc22eb..e12ae5f33 100644 --- a/LearningHub.Nhs.WebUI/Services/RegionService.cs +++ b/LearningHub.Nhs.WebUI/Services/RegionService.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/ResourceService.cs b/LearningHub.Nhs.WebUI/Services/ResourceService.cs index ec8eaee30..eb3370b7f 100644 --- a/LearningHub.Nhs.WebUI/Services/ResourceService.cs +++ b/LearningHub.Nhs.WebUI/Services/ResourceService.cs @@ -15,6 +15,8 @@ namespace LearningHub.Nhs.WebUI.Services using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.Models.Resource.ResourceDisplay; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.Extensions.Logging; diff --git a/LearningHub.Nhs.WebUI/Services/RoadMapService.cs b/LearningHub.Nhs.WebUI/Services/RoadMapService.cs index e63f9b9c1..5a16366ea 100644 --- a/LearningHub.Nhs.WebUI/Services/RoadMapService.cs +++ b/LearningHub.Nhs.WebUI/Services/RoadMapService.cs @@ -3,6 +3,8 @@ using System; using System.Threading.Tasks; using LearningHub.Nhs.Models.RoadMap; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/RoleService.cs b/LearningHub.Nhs.WebUI/Services/RoleService.cs index c4f37af65..bb75e4753 100644 --- a/LearningHub.Nhs.WebUI/Services/RoleService.cs +++ b/LearningHub.Nhs.WebUI/Services/RoleService.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; diff --git a/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs b/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs index 4df5f36e7..c8853e9f5 100644 --- a/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs +++ b/LearningHub.Nhs.WebUI/Services/SpecialtyService.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using elfhHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs b/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs index 6c7220499..b7bb5c93a 100644 --- a/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs +++ b/LearningHub.Nhs.WebUI/Services/TermsAndConditionsService.cs @@ -7,6 +7,8 @@ using elfhHub.Nhs.Models.Common; using elfhHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/LearningHub.Nhs.WebUI/Services/UserApiHttpClient.cs b/LearningHub.Nhs.WebUI/Services/UserApiHttpClient.cs index 399ad34cc..f16d5a311 100644 --- a/LearningHub.Nhs.WebUI/Services/UserApiHttpClient.cs +++ b/LearningHub.Nhs.WebUI/Services/UserApiHttpClient.cs @@ -2,6 +2,7 @@ { using System.Net.Http; using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Shared.Interfaces.Http; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Http; diff --git a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs index 22729e5df..ac0ac9bb3 100644 --- a/LearningHub.Nhs.WebUI/Services/UserGroupService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserGroupService.cs @@ -8,6 +8,8 @@ using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 04a2bf68f..799c91b7b 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -19,6 +19,8 @@ using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.User; using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Services; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; diff --git a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs index 0a0c04c49..7e0168253 100644 --- a/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs +++ b/LearningHub.Nhs.WebUI/Startup/ServiceMappings.cs @@ -1,19 +1,29 @@ namespace LearningHub.Nhs.WebUI.Startup { + using System; using System.Net.Http; + using Blazored.LocalStorage; using GDS.MultiPageFormData; using LearningHub.Nhs.Models.OpenAthens; using LearningHub.Nhs.Services; using LearningHub.Nhs.Services.Interface; + using LearningHub.Nhs.Shared.Interfaces.Http; + using LearningHub.Nhs.Shared.Interfaces.Services; + using LearningHub.Nhs.Shared.Services; + using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.JsDetection; using LearningHub.Nhs.WebUI.Services; using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; + using TELBlazor.Components.Core.Configuration; + using TELBlazor.Components.Core.Services.HelperServices; + using TELBlazor.Components.OptionalImplementations.Test.TestComponents.SearchExperiment; /// /// The service mappings. @@ -81,6 +91,13 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon // Config services.Configure(configuration.GetSection("OpenAthensScopes")); + services.Configure(configuration.GetSection("Settings:" + BFFPathValidationOptions.SectionName)); // qqqq + + // Blazor + services.AddRazorComponents() + .AddInteractiveServerComponents() + .AddCircuitOptions(opt => opt.DetailedErrors = true) + .AddInteractiveWebAssemblyComponents(); // Learning Hub Services services.AddTransient(); @@ -127,6 +144,36 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddScoped(); services.AddScoped(); services.AddScoped(); + + // + + // + // Future candidates for DI collection + services.AddBlazoredLocalStorage(); + + /* The base TELBlazor Configuration inherited by other components uses this configuration to tell blazor components ahead of time if the browser has Javascript (need to load the wasm and hydrate) via JsEnabled. + This allows for logic and UI to be implemented specifically for no js if desired without a second load of the component, where this may be desireable. + Host information is also provided which is useful for debugging. + */ + services.AddSingleton(provider => + { + var httpContextAccessor = provider.GetRequiredService(); + var context = httpContextAccessor.HttpContext; + bool jsEnabled = false; + + if (context != null && context.Request.Cookies.TryGetValue("jsEnabled", out var jsCookieValue)) + { + jsEnabled = jsCookieValue == "true"; + } + + return new TELBlazorBaseComponentConfiguration + { + JSEnabled = jsEnabled, + HostType = $"{configuration["Properties:Environment"]} {configuration["Properties:Application"]}", + }; + }); + + services.AddScoped(); } } } diff --git a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationGradePaging.cshtml b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationGradePaging.cshtml index 03d66e1a2..542fcc8de 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationGradePaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationGradePaging.cshtml @@ -1,7 +1,7 @@ @model LearningHub.Nhs.WebUI.Models.Account.AccountCreationPagingModel; @using System.Web; @using LearningHub.Nhs.WebUI.Models.Account -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @{ var pagingModel = Model; diff --git a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationJobRolePaging.cshtml b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationJobRolePaging.cshtml index 2b469af6b..cafd17275 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationJobRolePaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationJobRolePaging.cshtml @@ -1,7 +1,7 @@ @model LearningHub.Nhs.WebUI.Models.Account.AccountCreationPagingModel; @using System.Web; @using LearningHub.Nhs.WebUI.Models.Account -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @{ var pagingModel = Model; diff --git a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationRegionPaging.cshtml b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationRegionPaging.cshtml index f3f1c7506..b834f1ec9 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationRegionPaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationRegionPaging.cshtml @@ -1,7 +1,7 @@ @model LearningHub.Nhs.WebUI.Models.Account.AccountCreationPagingModel; @using System.Web; @using LearningHub.Nhs.WebUI.Models.Account -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @{ var pagingModel = Model; diff --git a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationSpecialtyPaging.cshtml b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationSpecialtyPaging.cshtml index ddeb89483..86114cc6d 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationSpecialtyPaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationSpecialtyPaging.cshtml @@ -1,7 +1,7 @@ @model LearningHub.Nhs.WebUI.Models.Account.AccountCreationPagingModel; @using System.Web; @using LearningHub.Nhs.WebUI.Models.Account -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @{ var pagingModel = Model; diff --git a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationWorkPlacePaging.cshtml b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationWorkPlacePaging.cshtml index 11080dada..052f4189b 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationWorkPlacePaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/_AccountCreationWorkPlacePaging.cshtml @@ -1,7 +1,7 @@ @model LearningHub.Nhs.WebUI.Models.Account.AccountCreationPagingModel; @using System.Web; @using LearningHub.Nhs.WebUI.Models.Account -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @{ var pagingModel = Model; diff --git a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml index 19d19e07a..d8fdfc493 100644 --- a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml @@ -1,4 +1,5 @@ @using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.Shared.Helpers @model LearningHub.Nhs.WebUI.Models.Bookmark.BookmarkItemViewModel @{ diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml index c3dbb7e45..e43637add 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/Index.cshtml @@ -1,3 +1,4 @@ +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.Models.Hierarchy @using LearningHub.Nhs.WebUI.Models; diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_ContentStructure.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_ContentStructure.cshtml index a39cfebcd..eb88ea682 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/_ContentStructure.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_ContentStructure.cshtml @@ -1,5 +1,6 @@ @using LearningHub.Nhs.Models.Enums; @using LearningHub.Nhs.Models.Hierarchy; +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Models.Catalogue; @model CatalogueIndexViewModel; diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchPagination.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchPagination.cshtml index dbccf1ee7..d417e4f1a 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchPagination.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchPagination.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.WebUtilities -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var pagingModel = Model.ResourceResultPaging; diff --git a/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchWithinCatalogue.cshtml b/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchWithinCatalogue.cshtml index eba2471d7..da9e95c6a 100644 --- a/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchWithinCatalogue.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Catalogue/_SearchWithinCatalogue.cshtml @@ -1,4 +1,4 @@ -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel
diff --git a/LearningHub.Nhs.WebUI/Views/Home/LandingPage.cshtml b/LearningHub.Nhs.WebUI/Views/Home/LandingPage.cshtml index 3a681c901..0fa63d1aa 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/LandingPage.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/LandingPage.cshtml @@ -37,6 +37,11 @@ Sign up, explore and learn
+ +
diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CatalogueCard.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CatalogueCard.cshtml index 341556517..90b803671 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CatalogueCard.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CatalogueCard.cshtml @@ -1,4 +1,5 @@ @using LearningHub.Nhs.Models.Dashboard +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.WebUI.Helpers @model DashboardCatalogueViewModel diff --git a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml index 68ad72564..beb9f4da3 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_CourseEnrolled.cshtml @@ -1,7 +1,9 @@ @using LearningHub.Nhs.Models.Dashboard +@using LearningHub.Nhs.Models.Moodle.API +@using LearningHub.Nhs.Shared.Helpers @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 8b8d25f8a..46b40fc1b 100644 --- a/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Home/_MyAccessedLearningTray.cshtml @@ -32,12 +32,14 @@ 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

- + + @@ -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/MyLearning/LearningCertificate.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningCertificate.cshtml index 120832cf7..83ef4c1e4 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/LearningCertificate.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/LearningCertificate.cshtml @@ -1,6 +1,7 @@ @using LearningHub.Nhs.Models.Enums @using LearningHub.Nhs.Models.Hierarchy @using LearningHub.Nhs.Models.Resource.ResourceDisplay +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Configuration; @using LearningHub.Nhs.WebUI.Models @using LearningHub.Nhs.WebUI.Models.Learning diff --git a/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml b/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml index 2a67789be..6c724d5d6 100644 --- a/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyLearning/_ActivityTablePaging.cshtml @@ -1,6 +1,6 @@ @using System.Web; @using LearningHub.Nhs.WebUI.Models.Learning -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @model MyLearningViewModel; diff --git a/LearningHub.Nhs.WebUI/Views/Resource/Resource.cshtml b/LearningHub.Nhs.WebUI/Views/Resource/Resource.cshtml index 91d3e63eb..987df7244 100644 --- a/LearningHub.Nhs.WebUI/Views/Resource/Resource.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Resource/Resource.cshtml @@ -1,5 +1,6 @@ @model ResourceIndexViewModel @using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.WebUI.Models @using LearningHub.Nhs.WebUI.Models.Resource diff --git a/LearningHub.Nhs.WebUI/Views/Resource/_ResourceInformation.cshtml b/LearningHub.Nhs.WebUI/Views/Resource/_ResourceInformation.cshtml index 98342b4db..478c1be2e 100644 --- a/LearningHub.Nhs.WebUI/Views/Resource/_ResourceInformation.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Resource/_ResourceInformation.cshtml @@ -1,5 +1,6 @@ @model ResourceIndexViewModel @using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Helpers @using LearningHub.Nhs.WebUI.Models @using LearningHub.Nhs.WebUI.Models.Resource diff --git a/LearningHub.Nhs.WebUI/Views/Resource/_ResourceItem.cshtml b/LearningHub.Nhs.WebUI/Views/Resource/_ResourceItem.cshtml index 0913435ff..9b568b55e 100644 --- a/LearningHub.Nhs.WebUI/Views/Resource/_ResourceItem.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Resource/_ResourceItem.cshtml @@ -1,5 +1,6 @@ @model ResourceIndexViewModel @using LearningHub.Nhs.Models.Enums +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Helpers @using LearningHub.Nhs.WebUI.Models @using LearningHub.Nhs.WebUI.Models.Resource diff --git a/LearningHub.Nhs.WebUI/Views/Search/Index.cshtml b/LearningHub.Nhs.WebUI/Views/Search/Index.cshtml index 4b8e0e6e3..f1ff3ea06 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/Index.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/Index.cshtml @@ -1,4 +1,4 @@ -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ ViewData["Title"] = "Search"; diff --git a/LearningHub.Nhs.WebUI/Views/Search/_CataloguePagination.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_CataloguePagination.cshtml index 04cd0a9c0..d06f7ff5e 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_CataloguePagination.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_CataloguePagination.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.WebUtilities -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var pagingModel = Model.CatalogueResultPaging; diff --git a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml index 55b297410..48888b2f3 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_CatalogueSearchResult.cshtml @@ -2,7 +2,7 @@ @using LearningHub.Nhs.WebUI.Extensions @using LearningHub.Nhs.Models.Search.SearchClick; -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var catalogueResult = Model.CatalogueSearchResult; diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceFilter.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceFilter.cshtml index 429eae962..a0867096a 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceFilter.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceFilter.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.WebUtilities -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var resourceResult = Model.ResourceSearchResult; diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourcePagination.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourcePagination.cshtml index b9b511dd5..563c052f9 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourcePagination.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourcePagination.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.WebUtilities -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var pagingModel = Model.ResourceResultPaging; diff --git a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml index 647583787..140a66b3e 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_ResourceSearchResult.cshtml @@ -1,13 +1,14 @@ -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @inject Microsoft.Extensions.Configuration.IConfiguration Configuration; @using System.Linq; @using System.Web; +@using LearningHub.Nhs.Shared.Helpers @using LearningHub.Nhs.WebUI.Helpers; @using LearningHub.Nhs.Models.Search; @using LearningHub.Nhs.Models.Search.SearchFeedback; @using LearningHub.Nhs.Models.Enums; -@using LearningHub.Nhs.WebUI.Models.Search; +@using LearningHub.Nhs.Shared.Models.Search; @using LearningHub.Nhs.Models.Search.SearchClick; @{ @@ -93,7 +94,7 @@
    Type: - @UtilityHelper.GetPrettifiedResourceTypeNameMoodle(UtilityHelper.ToEnum(item.ResourceType), 0) + @MoodleHelper.GetPrettifiedResourceTypeNameMoodle(UtilityHelper.ToEnum(item.ResourceType), 0)
    @if (item.ResourceType != "moodle") diff --git a/LearningHub.Nhs.WebUI/Views/Search/_SubmitFeedback.cshtml b/LearningHub.Nhs.WebUI/Views/Search/_SubmitFeedback.cshtml index a1ab36e1a..317772449 100644 --- a/LearningHub.Nhs.WebUI/Views/Search/_SubmitFeedback.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Search/_SubmitFeedback.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.WebUtilities -@model LearningHub.Nhs.WebUI.Models.Search.SearchResultViewModel +@model LearningHub.Nhs.Shared.Models.Search.SearchResultViewModel @{ var queryParams = QueryHelpers.ParseQuery(Context.Request.QueryString.ToString().ToLower()); diff --git a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml index 6b679b67e..c1dccf585 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/Tenant/LearningHub/_Layout.cshtml @@ -51,11 +51,13 @@ @await Html.PartialAsync("_GoogleAnalytics") + Skip to main content @@ -159,6 +161,7 @@ + @RenderSection("Scripts", required: false) diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_TwitterFeeds.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_TwitterFeeds.cshtml index 3ec036c6c..0d096a708 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_TwitterFeeds.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_TwitterFeeds.cshtml @@ -1,4 +1,4 @@ -@using LearningHub.Nhs.WebUI.Helpers; +@using LearningHub.Nhs.Shared.Helpers; @model List diff --git a/LearningHub.Nhs.WebUI/Views/_ViewImports.cshtml b/LearningHub.Nhs.WebUI/Views/_ViewImports.cshtml index 4af6553d8..b009df196 100644 --- a/LearningHub.Nhs.WebUI/Views/_ViewImports.cshtml +++ b/LearningHub.Nhs.WebUI/Views/_ViewImports.cshtml @@ -2,4 +2,4 @@ @using LearningHub.Nhs.WebUI.Models @using LearningHub.Nhs.WebUI.Helpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, NHSUKViewComponents.Web +@addTagHelper *, NHSUKViewComponents.Web \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/nuget.config.template b/LearningHub.Nhs.WebUI/nuget.config.template new file mode 100644 index 000000000..dac496006 --- /dev/null +++ b/LearningHub.Nhs.WebUI/nuget.config.template @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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..de178b6a2 --- /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.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index 727560e3f..e215135f9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + 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 000000000..4a98b4ebf --- /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/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..e366a4370 --- /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 000000000..73af93977 --- /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 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..90ffc4280 --- /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 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..30cb31a4e --- /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 401183bf6..61417224f 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": "" } } diff --git a/global.json b/global.json new file mode 100644 index 000000000..0974123a3 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "8.0.412" + } +} \ No newline at end of file