diff --git a/.gitignore b/.gitignore index 5e8ca2030..fcd06e014 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ obj /OpenAPI/LearningHub.Nhs.OpenApi/web.config /AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user /WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user +/ReportAPI/LearningHub.Nhs.ReportApi/web.config diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index b7d26718c..eba6b6ffa 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/mkiomediaplayer.ts b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/mkiomediaplayer.ts index 988dbdace..46704b4df 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/mkiomediaplayer.ts +++ b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/mkiomediaplayer.ts @@ -22,6 +22,7 @@ interface PlayerConfig { } interface SourceConfig { + enableLowLatency: boolean; hls: string; drm: { clearkey: ClearKeyConfig; @@ -57,6 +58,7 @@ function getSourceConfig( authenticationToken: string ): SourceConfig { return { + enableLowLatency: true, hls: locatorUri, drm: { clearkey: { @@ -81,6 +83,7 @@ function initializePlayer(videoContainer: HTMLElement, playerConfig: MKPlayerCon }; const sourceConfig: SourceConfig = { + enableLowLatency: true, hls: playBackUrl, drm: { clearkey: clearKeyConfig diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Services/MKIOMediaService.cs b/AdminUI/LearningHub.Nhs.AdminUI/Services/MKIOMediaService.cs index fb1860afd..dc44a6959 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Services/MKIOMediaService.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Services/MKIOMediaService.cs @@ -91,19 +91,21 @@ public async Task CreateMediaInputAsset(IFormFile file) /// . public async Task DownloadMediaInputAsset(string inputAssetName, string fileName) { - IAzureMediaServicesClient client = await this.CreateMediaServicesClientAsync(); + var client = this.GetMKIOServicesClientAsync(); + var assets = client.Assets.Get(inputAssetName); + + BlobServiceClient blobServiceClient = new BlobServiceClient(this.settings.MediaKindSettings.MediaKindStorageConnectionString); - AssetContainerSas assetContainerSas = await client.Assets.ListContainerSasAsync( - this.settings.AzureMediaResourceGroup, - this.settings.AzureMediaAccountName, - inputAssetName, - permissions: AssetContainerPermission.Read, - expiryTime: DateTime.UtcNow.AddHours(1).ToUniversalTime()); + BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(assets.Properties.Container); + if (!await containerClient.ExistsAsync().ConfigureAwait(false)) + { + await containerClient.CreateIfNotExistsAsync().ConfigureAwait(false); + } - string sasUri = assetContainerSas.AssetContainerSasUrls.First(); + var filename1 = Regex.Replace(fileName, "[^a-zA-Z0-9.]", string.Empty); + filename1 = string.IsNullOrEmpty(filename1) ? "file.txt" : filename1; - var blobServiceClient = new BlobContainerClient(new Uri(sasUri)); - var blobClient = blobServiceClient.GetBlockBlobClient(fileName); + BlobClient blobClient = containerClient.GetBlobClient(filename1); var fileContent = await blobClient.DownloadContentAsync(); return fileContent; diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/AccessibilityTestsBase.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/AccessibilityTestsBase.cs new file mode 100644 index 000000000..487b29b79 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/AccessibilityTestsBase.cs @@ -0,0 +1,60 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.Startup; + using OpenQA.Selenium; + using Selenium.Axe; + using Xunit; + + /// + /// Accessibility Tests Base. + /// + [Collection("Selenium test collection")] + public class AccessibilityTestsBase + { + /// + /// Gets the base URL for the tests. + /// + internal readonly string BaseUrl; + + /// + /// Gets the WebDriver used for the tests. + /// + internal readonly IWebDriver Driver; + + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public AccessibilityTestsBase(AccessibilityTestsFixture fixture) + { + this.BaseUrl = fixture.BaseUrl; + this.Driver = fixture.Driver; + } + + /// + /// Analyze Page Heading And Accessibility. + /// + /// Page Title. + public void AnalyzePageHeadingAndAccessibility(string pageTitle) + { + this.ValidatePageHeading(pageTitle); + + // then + // Exclude conditional radios, see: https://github.com/alphagov/govuk-frontend/issues/979#issuecomment-872300557 + var axeResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + axeResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + + /// + /// ValidatePageHeading. + /// + /// Page Title. + public void ValidatePageHeading(string pageTitle) + { + var h1Element = this.Driver.FindElement(By.TagName("h1")); + h1Element.Text.Should().BeEquivalentTo(pageTitle); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs new file mode 100644 index 000000000..09316a41c --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs @@ -0,0 +1,54 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using Selenium.Axe; + using Xunit; + + /// + /// BasicAccessibilityTests. + /// + public class BasicAccessibilityTests : AccessibilityTestsBase, IClassFixture + { + /// + /// Initializes a new instance of the class. + /// BasicAccessibilityTests. + /// + /// fixture. + public BasicAccessibilityTests(AccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// PageHasNoAccessibilityErrors. + /// + /// url to the page. + /// title of the page. + [Theory] + [InlineData("/forgotten-password", "Forgotten your username or password")] + [InlineData("/Login", "Access your Learning Hub account")] + [InlineData("/Home/Contactus", "Contact us")] + [InlineData("/Home/Aboutus", "About us")] + [InlineData("/Updates", "Service updates and releases")] + [InlineData("/Policies", "Our policies")] + [InlineData("/Home/Accessibility", "Accessibility Statement for the NHS England Learning Hub")] + [InlineData("/Home/NHSsites", "NHS sites")] + [InlineData("/policies/terms-and-conditions", "NHS England Learning Hub Terms and Conditions of Use (‘Terms’)")] + [InlineData("/policies/content-policy", "NHS England Learning Hub Content Policy")] + [InlineData("/policies/privacy-policy", "PRIVACY NOTICE")] + [InlineData("/policies/cookie-policy", "Cookie policy")] + [InlineData("/policies/acceptable-use-policy", "ACCEPTABLE USE POLICY")] + + public void PageHasNoAccessibilityErrors(string url, string pageTitle) + { + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + url); + + // then + this.AnalyzePageHeadingAndAccessibility(pageTitle); + + ////this.Driver.Dispose(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs new file mode 100644 index 000000000..92ab1811c --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BasicAuthenticatedAccessibilityTests.cs @@ -0,0 +1,47 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Microsoft.Azure.Management.Media.Models; + using OpenQA.Selenium; + using Xunit; + + /// + /// BasicAuthenticatedAccessibilityTests. + /// + public class BasicAuthenticatedAccessibilityTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// BasicAuthenticatedAccessibilityTests. + /// + /// fixture. + public BasicAuthenticatedAccessibilityTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// Authenticated Page Has no Accessibility Errors. + /// + /// url. + /// pageTitle. + [Theory] + [InlineData("/myaccount", "My account details")] + [InlineData("/MyLearning", "My learning")] + [InlineData("/allcatalogue", "A-Z of catalogues")] + [InlineData("/allcataloguesearch?term=primary#searchTab", "Search results for primary")] + public void AuthenticatedPageHasNoAccessibilityErrors(string url, string pageTitle) + { + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + url); + + // then + this.AnalyzePageHeadingAndAccessibility(pageTitle); + + // Dispose driver + ////this.Driver.LogOutUser(this.BaseUrl); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BookmarksTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BookmarksTests.cs new file mode 100644 index 000000000..b0964de50 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/BookmarksTests.cs @@ -0,0 +1,100 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Microsoft.AspNetCore.Components.Web; + using OpenQA.Selenium; + using Selenium.Axe; + using Xunit; + + /// + /// Bookmarks Tests. + /// + public class BookmarksTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public BookmarksTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// BookmarksPageHasNoAccessibilityErrors. + /// + [Fact] + public void BookmarksPageHasNoAccessibilityErrors() + { + // given + const string resourceUrl = "/Resource/17844/Item"; + const string resourceProdUrl = "/Resource/48180/Item"; + const string addBookmarkPageUrl = "/bookmark/resource?bookmarked=False&title=Understanding%20and%20managing%20conflict%20in%20children%27s%20healthcare&rri=16593&returnUrl=%2FResource%2F16593%2FItem"; + const string myBookmarksPage = "/bookmark"; + const string bookmarkname = "Primary care clinicians"; + IWebElement renameBookmarkElement = null; + IWebElement addBookmarkElement = null; + IWebElement moveBookmarkElement = null; + AxeResult addBookmarkPageResult = null; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + resourceProdUrl); + + var h1Element = this.Driver.FindElement(By.TagName("h1")); + if (h1Element.Text == "Unknown error" || h1Element.Text != bookmarkname) + { + this.Driver.Navigate().GoToUrl(this.BaseUrl + resourceUrl); + } + + try + { + addBookmarkElement = this.Driver.FindElement(By.XPath("//a[contains(text(),'Add to my bookmarks')]")); + if (addBookmarkElement.Displayed) + { + this.Driver.ClickLinkContainingText("Add to my bookmarks"); + this.ValidatePageHeading("Add bookmark"); + addBookmarkPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickButtonByText("Continue"); + } + } + catch (NoSuchElementException) + { + this.Driver.Navigate().GoToUrl(this.BaseUrl + addBookmarkPageUrl); + this.ValidatePageHeading("Add bookmark"); + addBookmarkPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Cancel"); + } + + this.Driver.Navigate().GoToUrl(this.BaseUrl + myBookmarksPage); + this.ValidatePageHeading("Bookmarked learning"); + var myBookmarksPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + this.Driver.ClickLinkContainingText("Add a folder"); + this.ValidatePageHeading("Add a folder"); + var addBookmarkFolderPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Cancel"); + + renameBookmarkElement = this.Driver.FindElement(By.XPath($"//tr[td//span[contains(text(), '{bookmarkname}')]]//td//div//form//span//button[contains(text(), 'Rename')]")); + renameBookmarkElement.Click(); + this.ValidatePageHeading("Rename bookmark"); + var renameBookmarkPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Cancel"); + + moveBookmarkElement = this.Driver.FindElement(By.XPath($"//tr[td//span[contains(text(), '{bookmarkname}')]]//td//div//form//span//button[contains(text(), 'Move')]")); + moveBookmarkElement.Click(); + this.ValidatePageHeading("Move your bookmark"); + var moveBookmarkPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Cancel"); + + // then + addBookmarkPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + myBookmarksPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + addBookmarkFolderPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + renameBookmarkPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + moveBookmarkPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CatalogueFolderContentTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CatalogueFolderContentTests.cs new file mode 100644 index 000000000..76b87944b --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CatalogueFolderContentTests.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Selenium.Axe; + using Xunit; + + /// + /// Catalogue Folder Content Tests. + /// + public class CatalogueFolderContentTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public CatalogueFolderContentTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// DashboardPageHasNoAccessibilityErrors. + /// + [Fact] + public void CatalogueFolderContentPageHasNoAccessibilityErrors() + { + // given + const string catalogueUrl = "/catalogue/Neonatal-AHP"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + catalogueUrl); + this.ValidatePageHeading("Enhanced Modules for Allied Health Professionals Working in Neonatal Care"); + var cataloguePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + ////this.Driver.ClickLinkContainingText("Manage this catalogue"); + ////this.ValidatePageHeading("Catalogue Management"); + ////var catalogueManagementPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + // then + cataloguePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CreateAccountTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CreateAccountTests.cs new file mode 100644 index 000000000..da149920a --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/CreateAccountTests.cs @@ -0,0 +1,186 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using OpenQA.Selenium; + using OpenQA.Selenium.Support.UI; + using Selenium.Axe; + using Xunit; + + /// + /// CreateAccountTests. + /// + public class CreateAccountTests : AccessibilityTestsBase, IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public CreateAccountTests(AccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// CreateFullUserAccountPageHasNoAccessibilityErrors. + /// + [Fact] + public void CreateFullUserAccountPageHasNoAccessibilityErrors() + { + // given + const string createAccountUrl = "/Registration/CreateAccountRegInfo"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl); + this.Driver.ClickLinkContainingText("Create new account"); + this.ValidatePageHeading("What you need to set up an account"); + var accountPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.ClickButtonByText("Continue"); + this.Driver.FindElement(By.XPath("//button[normalize-space(text())='Continue']")).Click(); + + // Verify the heading on the new page + this.ValidatePageHeading("Create an account"); + var emailVerificationPageResult = new AxeBuilder(this.Driver).Analyze(); + + this.Driver.FillTextInput("Email", "testuser4@nhs.net"); + this.Driver.FillTextInput("ComfirmEmail", "testuser4@nhs.net"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Full User Account"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Enter your personal details"); + var personalDetailsPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.FillTextInput("FirstName", "test"); + this.Driver.FillTextInput("LastName", "user"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your country"); + this.Driver.FillTextInput("FilterText", "England"); + this.Driver.SubmitForm(); + var countryPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("CountryId-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Select your region"); + var regionPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("RegionId-0"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your current role"); + this.Driver.FillTextInput("FilterText", "admin"); + this.Driver.SubmitForm(); + var rolePageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("CurrentRole-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Select your grade"); + var gradePageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("GradeId-0"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your primary specialty"); + var specialityPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("PrimarySpecialtyId-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Enter your start date"); + var startDatePageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.FillTextInput("Day", "1"); + this.Driver.FillTextInput("Month", "1"); + this.Driver.FillTextInput("Year", "2022"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your place of work"); + this.Driver.FillTextInput("FilterText", "london"); + this.Driver.SubmitForm(); + var placeOfWorkPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("LocationId-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Check your details"); + var summaryPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.Navigate().GoToUrl(this.BaseUrl); + + // then + emailVerificationPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + personalDetailsPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + countryPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + regionPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + rolePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + gradePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + specialityPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + startDatePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + placeOfWorkPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + summaryPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + + /// + /// CreateGeneralUserAccountPageHasNoAccessibilityErrors. + /// + [Fact] + public void CreateGeneralUserAccountPageHasNoAccessibilityErrors() + { + // given + const string createAccountUrl = "/Registration/CreateAccountRegInfo"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl); + ////this.Driver.Navigate().GoToUrl(this.BaseUrl + createAccountUrl); + this.Driver.ClickLinkContainingText("Create new account"); + this.ValidatePageHeading("What you need to set up an account"); + var accountPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.ClickButtonByText("Continue"); + this.Driver.FindElement(By.XPath("//button[normalize-space(text())='Continue']")).Click(); + + // Verify the heading on the new page + this.ValidatePageHeading("Create an account"); + var emailVerificationPageResult = new AxeBuilder(this.Driver).Analyze(); + + this.Driver.FillTextInput("Email", "testuser4@gmail.com"); + this.Driver.FillTextInput("ComfirmEmail", "testuser4@gmail.com"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("General User Account"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Enter your personal details"); + var personalDetailsPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.FillTextInput("FirstName", "test"); + this.Driver.FillTextInput("LastName", "user"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your country"); + this.Driver.FillTextInput("FilterText", "England"); + this.Driver.SubmitForm(); + var countryPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("CountryId-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Select your region"); + var regionPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("RegionId-0"); + this.Driver.SubmitForm(); + + this.ValidatePageHeading("Search for your current role"); + this.Driver.FillTextInput("FilterText", "admin"); + this.Driver.SubmitForm(); + var rolePageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.SelectRadioOptionById("CurrentRole-0"); + this.Driver.ClickButtonByText("Continue"); + + this.ValidatePageHeading("Check your details"); + var summaryPageResult = new AxeBuilder(this.Driver).Analyze(); + this.Driver.Navigate().GoToUrl(this.BaseUrl); + + // then + emailVerificationPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + personalDetailsPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + countryPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + regionPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + rolePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + summaryPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/DashboardTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/DashboardTests.cs new file mode 100644 index 000000000..c89ae663f --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/DashboardTests.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Selenium.Axe; + using Xunit; + + /// + /// Post login dashboard tests. + /// + public class DashboardTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public DashboardTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// DashboardPageHasNoAccessibilityErrors. + /// + [Fact] + public void DashboardPageHasNoAccessibilityErrors() + { + // given + const string dashboardUrl = "/?myLearningDashboard=my-in-progress&resourceDashboard=popular-resources&catalogueDashboard=popular-catalogues"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + dashboardUrl); + var axeResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + // then + CheckAccessibilityResult(axeResult); + } + + private static void CheckAccessibilityResult(AxeResult result) + { + result.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyAccountAccessibiltyTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyAccountAccessibiltyTests.cs new file mode 100644 index 000000000..5a2e85d33 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyAccountAccessibiltyTests.cs @@ -0,0 +1,133 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Selenium.Axe; + using Xunit; + + /// + /// MyAccountAccessibiltyTests. + /// + public class MyAccountAccessibiltyTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// MyAccountAccessibiltyTests. + /// + /// fixture. + public MyAccountAccessibiltyTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// MyAccount Page Has Accessibility Errors. + /// + [Fact] + public void MyAccountPageHasAccessibilityErrors() + { + // given + const string myaccountsUrl = "/myaccount"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + myaccountsUrl); + this.ValidatePageHeading("My account details"); + var myAccountPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + this.Driver.ClickLinkContainingText("first name"); + this.ValidatePageHeading("Update your first name"); + var firstNameChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + this.ValidatePageHeading("My account details"); + this.Driver.ClickLinkContainingText("last name"); + this.ValidatePageHeading("Update your last name"); + var lastNameChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("preferred name"); + this.ValidatePageHeading("Update your preferred name"); + var preferredNameChangeResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("country"); + this.ValidatePageHeading("Update your country"); + var countryChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("secondry email"); + this.ValidatePageHeading("Enter your new secondary email address"); + var emailChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("security question 1"); + this.ValidatePageHeading("Choose your first security question"); + var securityQuestChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("security question 2"); + this.ValidatePageHeading("Choose your second security question"); + var securityQuest2ChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("current role"); + this.ValidatePageHeading("Update current job role"); + var roleChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("grade"); + this.ValidatePageHeading("Update grade"); + var gradeChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("primary specialty"); + this.ValidatePageHeading("Update primary specialty"); + var primarySpecialityChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("start date"); + this.ValidatePageHeading("Update start date"); + var startDateChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + this.Driver.ClickLinkContainingText("place of work"); + this.ValidatePageHeading("Update place of work"); + var placeOfWorkChangePageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + this.Driver.ClickLinkContainingText("Go back"); + + // then + myAccountPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + firstNameChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + lastNameChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + preferredNameChangeResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + countryChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + ////regionChangePageResult.Violations.Should().BeEmpty(); + emailChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + securityQuestChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + securityQuest2ChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + roleChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + gradeChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + primarySpecialityChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + startDateChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + placeOfWorkChangePageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + ////CheckAccessibilityResult(result); + + // Dispose driver + ////this.Driver.LogOutUser(this.BaseUrl); + } + + private static void CheckAccessibilityResult(AxeResult result) + { + // Expect axe violation + result.Violations.Should().HaveCount(6); + + var violation = result.Violations[1]; + + violation.Id.Should().Be("landmark-contentinfo-is-top-level"); + violation.Nodes.Should().HaveCount(1); + violation.Nodes[0].Target.Should().HaveCount(1); + violation.Nodes[0].Target[0].Selector.Should().Be("footer > footer"); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyContributionsTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyContributionsTests.cs new file mode 100644 index 000000000..85c1bf38f --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/MyContributionsTests.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using Selenium.Axe; + using Xunit; + + /// + /// Contributions Tests. + /// + public class MyContributionsTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public MyContributionsTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// MyContributionsPageHasNoAccessibilityErrors. + /// + [Fact] + public void MyContributionsPageHasNoAccessibilityErrors() + { + // given + const string myContributionUrl = "/my-contributions"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + myContributionUrl); + var axeResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + // then + CheckAccessibilityResult(axeResult); + } + + private static void CheckAccessibilityResult(AxeResult result) + { + result.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/ResourcePagesTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/ResourcePagesTests.cs new file mode 100644 index 000000000..551e294a9 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/ResourcePagesTests.cs @@ -0,0 +1,64 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using OpenQA.Selenium; + using Selenium.Axe; + using Xunit; + + /// + /// SearchResultsTests. + /// + public class ResourcePagesTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public ResourcePagesTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// ResourcePagesHasNoAccessibilityErrors. + /// + /// url. + /// pageTitle. + [Theory] + [MemberData(nameof(GetResourcePageTestData))] + public void ResourcePagesHasNoAccessibilityErrors(string url, string pageTitle) + { + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + url); + + // then + var h1Element = this.Driver.FindElement(By.TagName("h1")); + if (h1Element.Text == "Unknown error" || h1Element.Text != pageTitle) + { + return; + } + + this.AnalyzePageHeadingAndAccessibility(pageTitle); + } + + /// + /// GetResourcePageTestData. + /// + /// resource url. + internal static IEnumerable GetResourcePageTestData() + { + yield return new object[] { "/Resource/309/Item", "IE11 Image test" }; + yield return new object[] { "/Resource/91/Item", "Removal and disposal of Personal Protective Equipment (PPE)" }; + yield return new object[] { "/Resource/15458/Item", "Test PDF File 16Dec" }; + yield return new object[] { "/Resource/17014/Item", "How to develop your teaching skills | BMJ Careers" }; + yield return new object[] { "/Resource/17760/Item", "HTML" }; + yield return new object[] { "/Resource/21430/Item", "MHA/MCA Interface Case Study (document)" }; + yield return new object[] { "/Resource/52338/Item", "How to develop your teaching skills | BMJ Careers" }; + yield return new object[] { "/Resource/56274/Item", "Phlebitis (HTML)" }; + yield return new object[] { "/Resource/27527/Item", "Pain after spinal cord injury - articles" }; + yield return new object[] { "/Resource/31888/Item", "360 Image of AMU Bed Bay" }; + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/SearchResultsTests.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/SearchResultsTests.cs new file mode 100644 index 000000000..8c995f02c --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/AccessibilityTests/SearchResultsTests.cs @@ -0,0 +1,47 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures; + using Selenium.Axe; + using Xunit; + + /// + /// SearchResultsTests. + /// + public class SearchResultsTests : AccessibilityTestsBase, + IClassFixture + { + /// + /// Initializes a new instance of the class. + /// + /// fixture. + public SearchResultsTests(AuthenticatedAccessibilityTestsFixture fixture) + : base(fixture) + { + } + + /// + /// SearchResultPageHasNoAccessibilityErrors. + /// + [Fact] + public void SearchResultPageHasNoAccessibilityErrors() + { + // given + const string searchResultUrl = "/search/results?term=primary"; + const string catalogueSearchResultUrl = "/catalogues?term=primary"; + + // when + this.Driver.Navigate().GoToUrl(this.BaseUrl + searchResultUrl); + this.ValidatePageHeading("Search results for primary"); + var searchResultPageResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + this.Driver.Navigate().GoToUrl(this.BaseUrl + catalogueSearchResultUrl); + ////this.ValidatePageHeading("Search results for primary"); + var catalogueSearchResult = new AxeBuilder(this.Driver).Exclude("div.nhsuk-radios--conditional div.nhsuk-radios__item input.nhsuk-radios__input").Analyze(); + + // then + searchResultPageResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + catalogueSearchResult.Violations.Where(v => !v.Tags.Contains("best-practice")).Should().BeEmpty(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/GlobalSuppressions.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/GlobalSuppressions.cs new file mode 100644 index 000000000..01866c145 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/GlobalSuppressions.cs @@ -0,0 +1,13 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Allowed", Scope = "member", Target = "~F:LearningHub.Nhs.WebUI.AutomatedUiTests.SeleniumServerFactory.RootUri")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Allowed", Scope = "member", Target = "~F:LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures.AccessibilityTestsFixture.Driver")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Allowed", Scope = "member", Target = "~F:LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures.AccessibilityTestsFixture.BaseUrl")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Allowed", Scope = "member", Target = "~F:LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests.AccessibilityTestsBase.BaseUrl")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Allowed", Scope = "member", Target = "~F:LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests.AccessibilityTestsBase.Driver")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Regions allowed", Scope = "member", Target = "~M:LearningHub.Nhs.WebUI.AutomatedUiTests.AccessibilityTests.BasicAccessibilityTests.PageHasNoAccessibilityErrors(System.String,System.String)")] diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj new file mode 100644 index 000000000..d6bf3cd0d --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + enable + enable + + false + + True + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/SeleniumServerFactory.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/SeleniumServerFactory.cs new file mode 100644 index 000000000..19edcdaef --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/SeleniumServerFactory.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests +{ + using System; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using LearningHub.Nhs.WebUI.Startup; + using Microsoft.AspNetCore; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Hosting.Server.Features; + using Microsoft.AspNetCore.Mvc.Testing; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Configuration.Json; + using Serilog; + + /// + /// SeleniumServerFactory. + /// + public class SeleniumServerFactory + { + /// + /// Root Uri. + /// + public string RootUri; + + /// + /// Initializes a new instance of the class. + /// + public SeleniumServerFactory() + { + IConfiguration configuration = ConfigurationHelper.GetConfiguration(); + this.RootUri = configuration["LearningHubWebUiUrl"]; + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AccessibilityTestsFixture.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AccessibilityTestsFixture.cs new file mode 100644 index 000000000..28eafbe22 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AccessibilityTestsFixture.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures +{ + using FluentAssertions; + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using OpenQA.Selenium; + + /// + /// Represents a fixture for accessibility tests. + /// + public class AccessibilityTestsFixture + { + /// + /// Gets the base URL for the tests. + /// + internal readonly string BaseUrl; + + /// + /// Gets the WebDriver used for the tests. + /// + internal readonly IWebDriver Driver; + + private readonly SeleniumServerFactory factory; + + /// + /// Initializes a new instance of the class. + /// + public AccessibilityTestsFixture() + { + this.factory = new SeleniumServerFactory(); + this.BaseUrl = this.factory.RootUri; + this.Driver = DriverHelper.CreateHeadlessChromeDriver(); + AssertionOptions.FormattingOptions.MaxLines = 500; + AssertionOptions.FormattingOptions.MaxDepth = 10; + } + + /// + /// Dispose. + /// + public void Dispose() + { + this.Driver.Quit(); + this.Driver.Dispose(); + ////Thread.Sleep(3000); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AuthenticatedAccessibilityTestsFixture.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AuthenticatedAccessibilityTestsFixture.cs new file mode 100644 index 000000000..9f16bed3b --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestFixtures/AuthenticatedAccessibilityTestsFixture.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.TestFixtures +{ + using LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers; + using Microsoft.Extensions.Configuration; + + /// + /// AuthenticatedAccessibilityTestsFixture. + /// + /// TStartup. + public class AuthenticatedAccessibilityTestsFixture : AccessibilityTestsFixture + { + /// + /// Initializes a new instance of the class. + /// + public AuthenticatedAccessibilityTestsFixture() + { + IConfiguration configuration = ConfigurationHelper.GetConfiguration(); + string adminUsername = configuration["AdminUser:Username"]; + string adminPassword = configuration["AdminUser:Password"]; + this.Driver.LogUserInAsAdmin(this.BaseUrl, adminUsername, adminPassword); + } + + /// + /// Dispose. + /// + public new void Dispose() + { + this.Driver.LogOutUser(this.BaseUrl); + this.Driver.Quit(); + base.Dispose(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/ConfigurationHelper.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/ConfigurationHelper.cs new file mode 100644 index 000000000..660575162 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/ConfigurationHelper.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers +{ + using Microsoft.Extensions.Configuration; + + /// + /// ConfigurationHelper. + /// + public static class ConfigurationHelper + { + /// + /// GetConfiguration. + /// + /// IConfiguration. + public static IConfiguration GetConfiguration() + { + return new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.Development.json") + .Build(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/DriverHelper.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/DriverHelper.cs new file mode 100644 index 000000000..ff60ad923 --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/DriverHelper.cs @@ -0,0 +1,109 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers +{ + using OpenQA.Selenium; + using OpenQA.Selenium.Chrome; + using OpenQA.Selenium.Support.UI; + + /// + /// Driver Helper. + /// + public static class DriverHelper + { + /// + /// Create Headless ChromeDriver. + /// + /// Chrome Driver. + public static ChromeDriver CreateHeadlessChromeDriver() + { + var chromeOptions = new ChromeOptions(); + + chromeOptions.AddArguments( + "--headless", + "no-sandbox"); + return new ChromeDriver(chromeOptions); + } + + /// + /// Fill Text Input. + /// + /// WebDriver. + /// inputId. + /// inputText. + public static void FillTextInput(this IWebDriver driver, string inputId, string inputText) + { + var answer = driver.FindElement(By.Id(inputId)); + answer.Clear(); + answer.SendKeys(inputText); + } + + /// + /// ClickButtonByText. + /// + /// WebDriver. + /// text. + public static void ClickButtonByText(this IWebDriver driver, string text) + { + var addButton = driver.FindElement(By.XPath($"//button[.='{text}']")); + addButton.Click(); + } + + /// + /// ClickLinkContainingText. + /// + /// WebDriver. + /// text. + public static void ClickLinkContainingText(this IWebDriver driver, string text) + { + var foundLink = driver.FindElement(By.XPath($"//a[contains(., '{text}')]")); + foundLink.Click(); + } + + /// + /// SelectDropdownItemValue. + /// + /// WebDriver. + /// dropdownId. + /// selectedValue. + public static void SelectDropdownItemValue(this IWebDriver driver, string dropdownId, string selectedValue) + { + var dropdown = new SelectElement(driver.FindElement(By.Id(dropdownId))); + dropdown.SelectByValue(selectedValue); + } + + /// + /// SetCheckboxState. + /// + /// WebDriver. + /// inputId. + /// checkState. + public static void SetCheckboxState(this IWebDriver driver, string inputId, bool checkState) + { + var answer = driver.FindElement(By.Id(inputId)); + if (answer.Selected != checkState) + { + answer.Click(); + } + } + + /// + /// Submit Form. + /// + /// WebDriver. + public static void SubmitForm(this IWebDriver driver) + { + var selectPromptForm = driver.FindElement(By.TagName("form")); + selectPromptForm.Submit(); + } + + /// + /// Select Radio Option By Id. + /// + /// WebDriver. + /// radio Id. + public static void SelectRadioOptionById(this IWebDriver driver, string radioId) + { + var radioInput = driver.FindElement(By.Id(radioId)); + radioInput.Click(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/LoginHelper.cs b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/LoginHelper.cs new file mode 100644 index 000000000..5b098b0be --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/TestHelpers/LoginHelper.cs @@ -0,0 +1,80 @@ +namespace LearningHub.Nhs.WebUI.AutomatedUiTests.TestHelpers +{ + using OpenQA.Selenium; + using OpenQA.Selenium.Support.UI; + + /// + /// LoginHelper. + /// + public static class LoginHelper + { + /// + /// Get LogUserInAsAdmin. + /// + /// WebDriver. + /// baseUrl. + /// adminName. + /// adminPassword. + public static void LogUserInAsAdmin(this IWebDriver driver, string baseUrl, string adminName, string adminPassword) + { + driver.Navigate().GoToUrl(baseUrl + "/Login"); + var username = driver.FindElement(By.Id("Username")); + username.SendKeys(adminName); + + var password = driver.FindElement(By.Id("Password")); + password.SendKeys(adminPassword); + + var submitButton = driver.FindElement(By.TagName("form")); + submitButton.Submit(); + } + + /// + /// LogOutUser. + /// + /// WebDriver. + /// baseUrl. + public static void LogOutUser(this IWebDriver driver, string baseUrl) + { + driver.Navigate().GoToUrl(baseUrl); + + try + { + // Maximum time to wait for the element in seconds + int maxWaitTimeInSeconds = 10; + + // Find the element using XPath + IWebElement logoutLink = null; + + for (int i = 0; i < maxWaitTimeInSeconds; i++) + { + try + { + logoutLink = driver.FindElement(By.CssSelector("a.nhsuk-account__login--link[href='/Home/Logout']")); + if (logoutLink.Displayed) + { + break; // Exit the loop if element is found and displayed + } + } + catch (NoSuchElementException) + { + // Element not found yet, wait for a second and try again + Thread.Sleep(1000); + } + } + + // Check if the element is found and displayed + if (logoutLink != null && logoutLink.Displayed) + { + // Perform an action on the element (e.g., click) + logoutLink.Click(); + } + } + finally + { + // Close the browser window + driver.Quit(); + driver.Dispose(); + } + } + } +} diff --git a/LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.json b/LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.json new file mode 100644 index 000000000..223c0c93f --- /dev/null +++ b/LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.json @@ -0,0 +1,7 @@ +{ + "LearningHubWebUiUrl": "", + "AdminUser": { + "Username": "", + "Password": "" + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI.sln b/LearningHub.Nhs.WebUI.sln index 9cce69d55..5aea6885f 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -79,6 +79,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.ReportApi.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.ReportApi.Shared", "ReportAPI\LearningHub.Nhs.ReportApi.Shared\LearningHub.Nhs.ReportApi.Shared.csproj", "{6167F037-166C-4C5A-81BE-55618E77D4E8}" 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -335,6 +337,14 @@ Global {6167F037-166C-4C5A-81BE-55618E77D4E8}.Release|Any CPU.Build.0 = Release|Any CPU {6167F037-166C-4C5A-81BE-55618E77D4E8}.Release|x64.ActiveCfg = Release|Any CPU {6167F037-166C-4C5A-81BE-55618E77D4E8}.Release|x64.Build.0 = Release|Any CPU + {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Debug|x64.Build.0 = Debug|Any CPU + {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index 9764d293e..ccb06c921 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -186,6 +186,16 @@ public Settings() /// public string GoogleAnalyticsId { get; set; } + /// + /// Gets or sets the PasswordRequestLimitingPeriod. + /// + public int PasswordRequestLimitingPeriod { get; set; } + + /// + /// Gets or sets the PasswordRequestLimit. + /// + public int PasswordRequestLimit { get; set; } + /// /// Gets or sets the SupportUrls. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs index 979c1ac33..b082eaee4 100644 --- a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs @@ -581,7 +581,18 @@ public async Task CreateAccountSearchRole(AccountCreationViewMode { var accountCreation = await this.multiPageFormService.GetMultiPageFormData(MultiPageFormDataFeature.AddRegistrationPrompt, this.TempData); - return this.View("CreateAccountSearchRole", new AccountCreationViewModel { CountryId = accountCreationViewModel.CountryId }); + var currentJobRole = int.TryParse(accountCreation.CurrentRole, out int currentRole); + if (currentJobRole && currentRole > 0) + { + var filterText = await this.jobRoleService.GetByIdAsync(currentRole); + accountCreationViewModel.FilterText = filterText.Name; + var jobrole = await this.jobRoleService.GetByIdAsync(currentRole); + return this.View("CreateAccountCurrentRole", new AccountCreationListViewModel { RoleList = new List { jobrole }, AccountCreationPaging = new AccountCreationPagingModel { TotalItems = 1, PageSize = UserRegistrationContentPageSize, HasItems = jobrole != null, CurrentPage = 1 }, CurrentRole = accountCreation.CurrentRole, CountryId = accountCreation.CountryId, RegionId = accountCreation.RegionId, FilterText = accountCreationViewModel.FilterText, ReturnToConfirmation = accountCreationViewModel.ReturnToConfirmation }); + } + else + { + return this.View("CreateAccountSearchRole", new AccountCreationViewModel { CountryId = accountCreationViewModel.CountryId }); + } } /// @@ -886,7 +897,7 @@ public async Task CreateAccountWorkStartDate(AccountCreationViewM } await this.multiPageFormService.SetMultiPageFormData(accountCreation, MultiPageFormDataFeature.AddRegistrationPrompt, this.TempData); - var dateVM = accountCreation.StartDate.HasValue ? new AccountCreationDateViewModel() { Day = accountCreation.StartDate.Value.Day, Month = accountCreation.StartDate.GetValueOrDefault().Month, Year = accountCreation.StartDate.Value.Year, FilterText = accountCreationViewModel.FilterText, ReturnToConfirmation = accountCreationViewModel.ReturnToConfirmation } : new AccountCreationDateViewModel() { FilterText = accountCreationViewModel.FilterText, ReturnToConfirmation = accountCreationViewModel.ReturnToConfirmation }; + var dateVM = accountCreation.StartDate.HasValue ? new AccountCreationDateViewModel() { Day = accountCreation.StartDate.HasValue ? accountCreation.StartDate.Value.Day.ToString() : string.Empty, Month = accountCreation.StartDate.HasValue ? accountCreation.StartDate.GetValueOrDefault().Month.ToString() : string.Empty, Year = accountCreation.StartDate.HasValue ? accountCreation.StartDate.Value.Year.ToString() : string.Empty, FilterText = accountCreationViewModel.FilterText, ReturnToConfirmation = accountCreationViewModel.ReturnToConfirmation } : new AccountCreationDateViewModel() { FilterText = accountCreationViewModel.FilterText, ReturnToConfirmation = accountCreationViewModel.ReturnToConfirmation }; if (!string.IsNullOrWhiteSpace(accountCreationViewModel.PrimarySpecialtyId) && string.IsNullOrWhiteSpace(accountCreationViewModel.FilterText)) { var specialty = this.specialtyService.GetSpecialtiesAsync().Result.FirstOrDefault(x => x.Id == specialtyId); @@ -948,7 +959,7 @@ public async Task CreateAccountStartDate(bool? returnToConfirmati } } - var dateVM = accountCreation.StartDate.HasValue ? new AccountCreationDateViewModel() { Day = accountCreation.StartDate.Value.Day, Month = accountCreation.StartDate.GetValueOrDefault().Month, Year = accountCreation.StartDate.Value.Year, ReturnToConfirmation = returnToConfirmation } : new AccountCreationDateViewModel() { ReturnToConfirmation = returnToConfirmation }; + var dateVM = accountCreation.StartDate.HasValue ? new AccountCreationDateViewModel() { Day = accountCreation.StartDate.HasValue ? accountCreation.StartDate.Value.Day.ToString() : string.Empty, Month = accountCreation.StartDate.HasValue ? accountCreation.StartDate.GetValueOrDefault().Month.ToString() : string.Empty, Year = accountCreation.StartDate.HasValue ? accountCreation.StartDate.Value.Year.ToString() : string.Empty, ReturnToConfirmation = returnToConfirmation } : new AccountCreationDateViewModel() { ReturnToConfirmation = returnToConfirmation }; return this.View("CreateAccountWorkStartDate", dateVM); } @@ -1187,8 +1198,20 @@ public async Task ForgotPassword(Models.Account.ForgotPasswordVie return this.Ok(new { duplicate = true }); } - await this.userService.ForgotPasswordAsync(model.EmailAddress); - return this.View("ForgotPasswordAcknowledgement"); + var passwordRequestLimitingPeriod = this.Settings.PasswordRequestLimitingPeriod; + var passwordRequestLimit = this.Settings.PasswordRequestLimit; + var status = await this.userService.CanRequestPasswordResetAsync(model.EmailAddress, passwordRequestLimitingPeriod, passwordRequestLimit); + if (status) + { + await this.userService.ForgotPasswordAsync(model.EmailAddress); + return this.View("ForgotPasswordAcknowledgement"); + } + else + { + this.ViewBag.Period = passwordRequestLimitingPeriod; + this.ViewBag.Limit = passwordRequestLimit; + return this.View("TooManyRequests"); + } } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index 87d01e668..275fe9901 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -15,13 +15,13 @@ namespace LearningHub.Nhs.WebUI.Controllers using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; - using Microsoft.ApplicationInsights.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.FeatureManagement; @@ -39,6 +39,7 @@ public class HomeController : BaseController private readonly IDashboardService dashboardService; private readonly IContentService contentService; private readonly IFeatureManager featureManager; + private readonly Microsoft.Extensions.Configuration.IConfiguration configuration; /// /// Initializes a new instance of the class. @@ -53,6 +54,7 @@ public class HomeController : BaseController /// Dashboard service. /// Content service. /// featureManager. + /// config. public HomeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -63,7 +65,8 @@ public HomeController( LearningHubAuthServiceConfig authConfig, IDashboardService dashboardService, IContentService contentService, - IFeatureManager featureManager) + IFeatureManager featureManager, + Microsoft.Extensions.Configuration.IConfiguration configuration) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.authConfig = authConfig; @@ -72,6 +75,7 @@ public HomeController( this.dashboardService = dashboardService; this.contentService = contentService; this.featureManager = featureManager; + this.configuration = configuration; } /// @@ -133,11 +137,12 @@ public IActionResult CreateAccount() public IActionResult Error(int? httpStatusCode) { string originalPathUrlMessage = null; - + string originalPath = null; if (httpStatusCode.HasValue && httpStatusCode.Value == 404) { var exceptionHandlerPathFeature = this.HttpContext.Features.Get(); - originalPathUrlMessage = $"Page Not Found url: {exceptionHandlerPathFeature?.OriginalPath}. "; + originalPath = exceptionHandlerPathFeature?.OriginalPath; + originalPathUrlMessage = $"Page Not Found url: {originalPath}. "; } if (this.User.Identity.IsAuthenticated) @@ -320,14 +325,19 @@ public IActionResult NhsSites() } /// - /// The Logout. - /// This is directly referenced in the LoginWizardFilter to allow - /// logouts to bypass LoginWizard redirects. - /// If the name is changed, the LoginWizardFilter MUST be updated. + /// The ChangePasswordAcknowledgement. /// /// The . - [AllowAnonymous] - public IActionResult Logout() + public IActionResult ChangePasswordAcknowledgement() + { + return this.View(); + } + + /// + /// StatusUpdate. + /// + /// Actionresult. + public IActionResult UserLogout() { if (!(this.User?.Identity.IsAuthenticated ?? false)) { @@ -337,6 +347,20 @@ public IActionResult Logout() return new SignOutResult(new[] { CookieAuthenticationDefaults.AuthenticationScheme, "oidc" }); } + /// + /// The Logout. + /// This is directly referenced in the LoginWizardFilter to allow + /// logouts to bypass LoginWizard redirects. + /// If the name is changed, the LoginWizardFilter MUST be updated. + /// + /// The . + [AllowAnonymous] + public IActionResult Logout() + { + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=true"; + return this.Redirect(redirectUri); + } + /// /// The SessionTimeout. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index 4494d9463..8711dd817 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -21,10 +21,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NHSUKViewComponents.Web.ViewModels; using ChangePasswordViewModel = LearningHub.Nhs.WebUI.Models.UserProfile.ChangePasswordViewModel; + using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration; /// /// The UserController. @@ -43,6 +45,7 @@ public partial class MyAccountController : BaseController private readonly ISpecialtyService specialtyService; private readonly ILocationService locationService; private readonly ICacheService cacheService; + private readonly IConfiguration configuration; /// /// Initializes a new instance of the class. @@ -61,6 +64,7 @@ public partial class MyAccountController : BaseController /// The locationService. /// The multiPageFormService. /// The cacheService. + /// The cacheService. public MyAccountController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -75,7 +79,8 @@ public MyAccountController( ISpecialtyService specialtyService, ILocationService locationService, IMultiPageFormService multiPageFormService, - ICacheService cacheService) + ICacheService cacheService, + IConfiguration configuration) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.userService = userService; @@ -88,6 +93,7 @@ public MyAccountController( this.locationService = locationService; this.multiPageFormService = multiPageFormService; this.cacheService = cacheService; + this.configuration = configuration; } private string LoginWizardCacheKey => $"{this.CurrentUserId}:LoginWizard"; @@ -452,9 +458,8 @@ public async Task UpdatePassword(ChangePasswordViewModel model) if (this.ModelState.IsValid) { await this.userService.UpdatePassword(model.NewPassword); - - this.ViewBag.SuccessMessage = CommonValidationErrorMessages.PasswordSuccessMessage; - return this.View("SuccessMessage"); + var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=false"; + return this.Redirect(redirectUri); } else { diff --git a/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs b/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs new file mode 100644 index 000000000..4d17cc5b7 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/InMemoryTicketStore.cs @@ -0,0 +1,104 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + + /// + /// Defines the . + /// + public class InMemoryTicketStore : ITicketStore + { + private readonly ConcurrentDictionary cache; + + /// + /// Initializes a new instance of the class. + /// The InMemoryTicketStore. + /// + /// the cache. + public InMemoryTicketStore(ConcurrentDictionary cache) + { + this.cache = cache; + } + + /// + /// The StoreAsync. + /// + /// The ticket. + /// The key. + public async Task StoreAsync(AuthenticationTicket ticket) + { + var ticketUserId = ticket.Principal.Claims.Where(c => c.Type == "sub") + .FirstOrDefault() + .Value; + var matchingAuthTicket = this.cache.Values.FirstOrDefault( + t => t.Principal.Claims.FirstOrDefault( + c => c.Type == "sub" + && c.Value == ticketUserId) != null); + if (matchingAuthTicket != null) + { + var cacheKey = this.cache.Where( + entry => entry.Value == matchingAuthTicket) + .Select(entry => entry.Key) + .FirstOrDefault(); + this.cache.TryRemove( + cacheKey, + out _); + } + + var key = Guid + .NewGuid() + .ToString(); + await this.RenewAsync( + key, + ticket); + return key; + } + + /// + /// The RenewAsync. + /// + /// The key. + /// The ticket. + /// The Task. + public Task RenewAsync( + string key, + AuthenticationTicket ticket) + { + this.cache.AddOrUpdate( + key, + ticket, + (_, _) => ticket); + return Task.CompletedTask; + } + + /// + /// The RetrieveAsync. + /// + /// The Key. + /// The Task. + public Task RetrieveAsync(string key) + { + this.cache.TryGetValue( + key, + out var ticket); + return Task.FromResult(ticket); + } + + /// + /// The RemoveAsync. + /// + /// The key. + /// The Task. + public Task RemoveAsync(string key) + { + this.cache.TryRemove( + key, + out _); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs index 0b9dd4b77..1efd29ad5 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IUserService.cs @@ -418,6 +418,15 @@ public interface IUserService /// A representing the result of the asynchronous operation. Task RegenerateEmailChangeValidationTokenAsync(string newPrimaryEmail, bool isUserRoleUpgrade); + /// + /// User Can request for password reset. + /// + /// The email Address. + /// The passwordRequestLimitingPeriod. + /// ThepasswordRequestLimit. + /// A representing the result of the asynchronous operation. + Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit); + /// /// GenerateEmailChangeValidationTokenAndSendEmail. /// diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 8e0b11cce..1b4ac83d1 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -104,6 +104,7 @@ + @@ -112,7 +113,7 @@ - + diff --git a/LearningHub.Nhs.WebUI/Models/Account/AccountCreationDateViewModel.cs b/LearningHub.Nhs.WebUI/Models/Account/AccountCreationDateViewModel.cs index c17fad8a9..68727eb35 100644 --- a/LearningHub.Nhs.WebUI/Models/Account/AccountCreationDateViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/Account/AccountCreationDateViewModel.cs @@ -13,17 +13,32 @@ public class AccountCreationDateViewModel : AccountCreationViewModel, IValidatab /// /// Gets or sets the Day. /// - public int? Day { get; set; } + public string Day { get; set; } + + /// + /// Gets or sets the Day Field. + /// + public int? DayInput { get; set; } /// /// Gets or sets the Country. /// - public int? Month { get; set; } + public string Month { get; set; } + + /// + /// Gets or sets the Month input. + /// + public int? MonthInput { get; set; } /// /// Gets or sets the Year. /// - public int? Year { get; set; } + public string Year { get; set; } + + /// + /// Gets or sets YearInput. + /// + public int? YearInput { get; set; } /// /// Gets or sets the GetDate. @@ -31,14 +46,46 @@ public class AccountCreationDateViewModel : AccountCreationViewModel, IValidatab /// DateTime. public DateTime? GetDate() { - return (this.Day.HasValue && this.Month.HasValue && this.Year.HasValue) ? new DateTime(this.Year!.Value, this.Month!.Value, this.Day!.Value) : (DateTime?)null; + return (this.DayInput.HasValue && this.MonthInput.HasValue && this.YearInput.HasValue) ? new DateTime(this.YearInput!.Value, this.MonthInput!.Value, this.DayInput!.Value) : (DateTime?)null; } /// public IEnumerable Validate(ValidationContext validationContext) { - return DateValidator.ValidateDate(this.Day, this.Month, this.Year, "valid start date") - .ToValidationResultList(nameof(this.Day), nameof(this.Month), nameof(this.Year)); + var validationResults = new List(); + int parsedDay = 0; + int parsedMonth = 0; + int parsedYear = 0; + + if (!string.IsNullOrWhiteSpace(this.Day) && !int.TryParse(this.Day, out parsedDay)) + { + validationResults.Add(new ValidationResult( + $"The value '{this.Day}' is not valid for Day.", new[] { nameof(this.Day) })); + } + + if (!string.IsNullOrWhiteSpace(this.Month) && !int.TryParse(this.Month, out parsedMonth)) + { + validationResults.Add(new ValidationResult( + $"The value '{this.Month}' is not valid for Month.", new[] { nameof(this.Month) })); + } + + if (!string.IsNullOrWhiteSpace(this.Year) && !int.TryParse(this.Year, out parsedYear)) + { + validationResults.Add(new ValidationResult( + $"The value '{this.Year}' is not valid for Year.", new[] { nameof(this.Year) })); + } + + if (validationResults.Count > 0) + { + return validationResults; + } + + this.DayInput = parsedDay; + this.MonthInput = parsedMonth; + this.YearInput = parsedYear; + + return DateValidator.ValidateDate(this.DayInput, this.MonthInput, this.YearInput, "valid start date") + .ToValidationResultList(nameof(this.Day), nameof(this.Month), nameof(this.Year)); } } } diff --git a/LearningHub.Nhs.WebUI/Program.cs b/LearningHub.Nhs.WebUI/Program.cs index 8aee50089..c24d9057c 100644 --- a/LearningHub.Nhs.WebUI/Program.cs +++ b/LearningHub.Nhs.WebUI/Program.cs @@ -84,7 +84,6 @@ app.UseAuthorization(); app.UseMiddleware(); - app.UseStaticFiles(); app.Map(TimezoneInfoMiddleware.TimezoneInfoUrl, b => b.UseMiddleware()); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/mkiomediaplayer.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/mkiomediaplayer.ts index 17c48d8fb..f5b76ec6d 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/mkiomediaplayer.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/mkiomediaplayer.ts @@ -24,6 +24,7 @@ interface PlayerConfig { } interface SourceConfig { + enableLowLatency: boolean; hls: string; drm: { clearkey: ClearKeyConfig; @@ -64,6 +65,7 @@ function getPlayerConfig(mkioKey: string, onPlayerReady: () => void, onSubtitleA function initializePlayer(videoContainer: HTMLElement, playerConfig: MKPlayerConfig, playBackUrl: string): any { const player = new MKPlayer(videoContainer, playerConfig); const sourceConfig: SourceConfig = { + enableLowLatency: true, hls: playBackUrl, drm: { clearkey: getClearKeyConfig() diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue index 9820ef63e..25ef12656 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notification.vue @@ -30,7 +30,7 @@ {{ notification.title }} - {{ item.text }} @@ -45,7 +45,7 @@ - Delete + Delete @@ -74,7 +74,10 @@ {{ notification.title }} - Delete + + + + @@ -170,7 +173,7 @@ .catch(e => console.log(e)); } }, - async deleteNotification() { + async deleteNotification() { await axios.delete( '/api/notification/' + this.selectedNotification.notificationId + '?userNotificationId=' + this.selectedNotification.id) .catch(e => console.log(e)); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notifications.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notifications.vue index 0a338d282..2c3b9b0b1 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notifications.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/notification/notifications.vue @@ -86,7 +86,7 @@ - + @@ -97,7 +97,7 @@ @@ -142,10 +142,17 @@ $('#deleteModalButton').modal('hide'); $('#deleteModal').modal(); }, + closeConfirmation() { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + document.body.focus(); + setTimeout(() => { $('#deleteModal').modal('hide'); }, 10); + }, showNotification(notification: NotificationModel) { this.selectedNotification = notification; this.showMessage = true; - setTimeout(() => { $('html,body').scrollTop($("#backToList").offset().top); }, 100); + setTimeout(() => { $('html,body').scrollTop($(".nhsuk-back-link").offset().top); }, 100); }, showNotificationList(event: any) { @@ -154,7 +161,7 @@ }, async deleteNotification() { await this.getSelectedComponent().deleteNotification(); - $('#deleteModal').modal('hide'); + this.closeConfirmation(); this.showMessage = false; }, notificationTypeContent() { diff --git a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs index 908008a70..5ed5cc926 100644 --- a/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs @@ -3,6 +3,7 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Net; + using AspNetCoreRateLimit; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Binders; using LearningHub.Nhs.Models.Enums; diff --git a/LearningHub.Nhs.WebUI/Services/UserService.cs b/LearningHub.Nhs.WebUI/Services/UserService.cs index 83b8f64d8..942360eae 100644 --- a/LearningHub.Nhs.WebUI/Services/UserService.cs +++ b/LearningHub.Nhs.WebUI/Services/UserService.cs @@ -1640,6 +1640,31 @@ public async Task RegenerateEmailChangeVali return viewmodel; } + /// + public async Task CanRequestPasswordResetAsync(string emailAddress, int passwordRequestLimitingPeriod, int passwordRequestLimit) + { + bool status = false; + + var client = await this.LearningHubHttpClient.GetClientAsync(); + + var request = $"User/CanRequestPasswordReset/{emailAddress}/{passwordRequestLimitingPeriod}/{passwordRequestLimit}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + status = JsonConvert.DeserializeObject(result); + } + else if (response.StatusCode == HttpStatusCode.Unauthorized + || + response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return status; + } + /// public async Task GenerateEmailChangeValidationTokenAndSendEmailAsync(string emailAddress, bool isUserRoleUpgrade) { diff --git a/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs b/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs index 40caab241..2994bd3b5 100644 --- a/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs +++ b/LearningHub.Nhs.WebUI/Startup/AuthenticationConfiguration.cs @@ -1,12 +1,14 @@ namespace LearningHub.Nhs.WebUI.Startup { using System; + using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using IdentityModel; using LearningHub.Nhs.Caching; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Handlers; + using LearningHub.Nhs.WebUI.Helpers; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/bookmark.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/bookmark.scss index 6378eae15..126c5c35f 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/bookmark.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/bookmark.scss @@ -112,6 +112,23 @@ td.col-type { tr.bookmark-item:hover td { background-color: $nhsuk-white; } +.my-bookmark-btn:focus { + outline: none; + text-decoration: none !important; + color: $nhsuk-black !important; + background-color: $nhsuk-yellow !important; + box-shadow: 0 -2px $govuk-focus-highlight-yellow, 0 4px $nhsuk-black; +} + +.my-bookmark-btn:hover { + outline: none; + text-decoration: none !important; +} + +.my-bookmark-btn { + transition: none !important; + border-radius: 0 !important; +} @media (min-width: 768px) { diff --git a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/search.scss b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/search.scss index 4cc9653d5..31a2d0d24 100644 --- a/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/search.scss +++ b/LearningHub.Nhs.WebUI/Styles/nhsuk/pages/search.scss @@ -44,7 +44,7 @@ justify-content: space-between; .nhsuk-list { - width: 360px; + width: 378px; li { width: 33.33%; diff --git a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialty.cshtml b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialty.cshtml index 0bc9979cf..fb35d21ad 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialty.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialty.cshtml @@ -63,7 +63,7 @@ - + Not applicable diff --git a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialtySelection.cshtml b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialtySelection.cshtml index 495df4081..393d5c76a 100644 --- a/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialtySelection.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Account/CreateAccountPrimarySpecialtySelection.cshtml @@ -145,7 +145,7 @@ - + Not applicable diff --git a/LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml b/LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml new file mode 100644 index 000000000..fbc8a468d --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Account/TooManyRequests.cshtml @@ -0,0 +1,16 @@ +@{ + ViewData["Title"] = "Reset limit reached"; + // Get the value from ViewBag + var period = ViewBag.Period.ToString(); +} + + + + + @ViewData["Title"] + You've requested the maximum number of password resets (@ViewBag.Limit). Please wait @period minutes before trying again or contact the support team. + @DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss") + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Bookmark/Move.cshtml b/LearningHub.Nhs.WebUI/Views/Bookmark/Move.cshtml index 462133de2..10677a0b6 100644 --- a/LearningHub.Nhs.WebUI/Views/Bookmark/Move.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Bookmark/Move.cshtml @@ -52,7 +52,7 @@ } - + @@ -72,6 +72,7 @@ required="@(Model.SelectedFolderId == 0)" /> + @if (Model.Bookmark.ParentId.HasValue || (Model.Folders != null && Model.Folders.Any())) { diff --git a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkAction.cshtml b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkAction.cshtml index 5349fbb30..e3b338884 100644 --- a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkAction.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkAction.cshtml @@ -6,7 +6,7 @@ @if (Model.IsFirst == false) { - + } @@ -15,25 +15,25 @@ @if (Model.IsLast == false) { - + } - Rename + Rename @if (Model.IsFolder == false) { - Move + Move } - Delete + Delete \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml index d3698d691..19d19e07a 100644 --- a/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Bookmark/_BookmarkItem.cshtml @@ -24,11 +24,11 @@ @Model.Bookmark.Title - + Type @GetResourceTypeName(Model.Bookmark.BookmarkTypeId, Model.Bookmark.ResourceTypeId) - + Organise diff --git a/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml b/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml new file mode 100644 index 000000000..28bf1b66a --- /dev/null +++ b/LearningHub.Nhs.WebUI/Views/Home/ChangePasswordAcknowledgement.cshtml @@ -0,0 +1,24 @@ +@{ + ViewData["Title"] = "Change password Acknowledgement"; +} + + + + + + + Your password has been changed successfully. + + + You have been logged out. Please log in using your username and new password to explore the Learning Hub. + + + + Continue to the Learning Hub + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLastName.cshtml b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLastName.cshtml index 4b4167544..9212175be 100644 --- a/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLastName.cshtml +++ b/LearningHub.Nhs.WebUI/Views/MyAccount/ChangeLastName.cshtml @@ -18,7 +18,7 @@ Change last name - Update your last name + Update your last name @if (Model.ShowSearch) { - + } @if (Model.ShowSearch) diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml index 91a06a32d..e87879ce7 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_LoginBanner.cshtml @@ -15,7 +15,7 @@ class="nhsuk-button nhsuk-button--secondary">OpenAthens Log in - Forgotten username or password + Forgotten username or password Create new account diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml index 14256a335..594c62f7c 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml @@ -49,7 +49,7 @@ @if (User.Identity.IsAuthenticated) { - + Menu diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index b459b9b6b..e67ceaa8b 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -37,6 +37,8 @@ "KeepUserSessionAliveIntervalMins": 15, "SecurityQuestionsToAsk": 2, "Restricted": false, + "PasswordRequestLimitingPeriod": 1, // minutes + "PasswordRequestLimit": 2, "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" @@ -113,7 +115,7 @@ }, "EnableTempDebugging": "false", "LimitScormToAdmin": "false" - + }, "LearningHubAuthServiceConfig": { "Authority": "", @@ -158,5 +160,5 @@ "FeatureManagement": { "ContributeAudioVideoResource": true, "DisplayAudioVideoResource": true - } + } } diff --git a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js b/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js deleted file mode 100644 index 31cd8f56e..000000000 --- a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js +++ /dev/null @@ -1,106 +0,0 @@ -var WebUI = WebUI || {}; -WebUI.PagingRequest = { Page: 1, SortColumn: '', SortDirection: '', Filter: [] }; - -WebUI.pagingSetup = function (sortColumn, sortDirection, filter) { - - WebUI.PagingRequest.SortColumn = sortColumn; - WebUI.PagingRequest.SortDirection = sortDirection; - if (!filter) { filter = []; } - WebUI.PagingRequest.Filter = filter; - - // Add Click events to column headers - $('table.lh-datatable th.orderable').attr('onClick', 'WebUI.changeTableOrder(this);'); - - // Add asc & desc class - $('table.lh-datatable th[data-column="' + sortColumn + '"].orderable').addClass(sortDirection === 'D' ? 'desc' : 'asc'); - - // Add filter row - var columns = $('table.lh-datatable.filtered thead tr th'); - if (columns.length > 0) { - row = document.createElement('tr'); - $(row).addClass('filter-row'); - - for (var i = 0; i < columns.length; i++) { - var columnName = $(columns[i]).attr('data-column'); - //var filterColumn = filter.find(x => x.Column === columnName); fails in IE11 - var filterColumn = filter.filter(function (x) { return x.Column === columnName; })[0]; - var filterValue = ''; - if (filterColumn) { filterValue = filterColumn.Value; } - var inputType = 'text'; - if ($(columns[i]).attr('data-type')) { inputType = $(columns[i]).attr('data-type'); }; - - if (columnName) { - $(row).append(''); - } else { - $(row).append(''); - } - //console.log('Add cell :' + i); - } - $('table.lh-datatable tbody').prepend(row); - - // Add click event to filter buttons - $('table.lh-datatable tr.filter-row td button').attr('onClick', 'WebUI.filterTable();'); - $('table.lh-datatable tr.filter-row td input').keypress(function (e) { - if (e.which === 13) { - WebUI.filterTable(); - return false; - } - }); - $('table.lh-datatable tr.filter-row td input')[0].focus(); - } -}; - -WebUI.requestPage = function (page) { - WebUI.PagingRequest.Page = page; - WebUI.retrievePage(); -}; - -WebUI.changeTableOrder = function (headerLink) { - var sortDirection = 'A'; - var sortColumn = $(headerLink).attr('data-column'); - var currentSortDirection = $(headerLink).hasClass('asc'); - - if (currentSortDirection) { sortDirection = 'D'; } - - WebUI.PagingRequest.SortDirection = sortDirection; - WebUI.PagingRequest.SortColumn = sortColumn; - - WebUI.retrievePage(); -}; - -WebUI.filterTable = function () { - WebUI.PagingRequest.Filter = []; - $('table.lh-datatable.filtered tbody tr.filter-row td input').each(function (index) { - var filterColumn = $(this).attr('data-column'); - var filterValue = $(this).val(); - if ($(this).val() !== '') { - WebUI.PagingRequest.Filter.push({ Column: filterColumn, Value: filterValue }); - } - }); - WebUI.retrievePage(); -}; - -WebUI.clearFilters = function () { - WebUI.PagingRequest.Page = 1; - WebUI.PagingRequest.Filter = []; - WebUI.retrievePage(); - return false; -}; - -WebUI.retrievePage = function () { - var url = window.location.pathname; - - var form = $(document.createElement('form')); - $(form).attr("action", url); - $(form).attr("method", "POST"); - - var pagingRequestString = JSON.stringify(WebUI.PagingRequest); - var input = $("") - .attr("type", "hidden") - .attr("name", "pagingRequestModel") - .val(pagingRequestString); - - $(form).append($(input)); - form.appendTo(document.body); - $(form).submit(); -}; \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs new file mode 100644 index 000000000..12d6da133 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureBlobSettings. + /// + public class AzureBlobSettings + { + /// + /// Gets or sets the connectionString. + /// + public string ConnectionString { get; set; } = null!; + + /// + /// Gets or sets the catalogue collection id. + /// + public string UploadContainer { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs new file mode 100644 index 000000000..9b3ad76d1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureConfig. + /// + public class AzureConfig + { + /// + /// Gets or sets the azure blob settings. + /// + public AzureBlobSettings AzureBlobSettings { get; set; } = null!; + /// + /// Gets or sets the azure storage queue. + /// + public string AzureStorageQueueConnectionString { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs index 3e97bbb53..ece9a6012 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs @@ -14,5 +14,10 @@ public class FindwiseCollectionIdSettings /// Gets or sets the catalogue collection id. /// public string Catalogue { get; set; } = null!; + + /// + /// Gets or sets the AutoSuggestion collection id. + /// + public string AutoSuggestion { get; set; } = null!; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs index dd84799c5..009509127 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseConfig.cs @@ -5,6 +5,11 @@ namespace LearningHub.Nhs.OpenApi.Models.Configuration /// public class FindwiseConfig { + /// + /// Gets or sets the base url for the Findwise Index endpoint. + /// + public string IndexUrl { get; set; } = null!; + /// /// Gets or sets the base url for the Findwise search service. /// @@ -25,6 +30,16 @@ public class FindwiseConfig /// public int DefaultItemLimitForSearch { get; set; } + /// + /// Gets or sets the description limit. + /// + public int DescriptionLengthLimit { get; set; } + + /// + /// Gets or sets the description length. + /// + public int MaximumDescriptionLength { get; set; } + /// /// Gets or sets the collection ids. /// @@ -34,5 +49,10 @@ public class FindwiseConfig /// Gets or sets the special search characters. /// public string SpecialSearchCharacters { get; set; } = null!; + + /// + /// Gets or sets the index method. + /// + public string IndexMethod { get; set; } = null!; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs index 9e2086ee3..6ccd4f37c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs @@ -14,5 +14,46 @@ public class LearningHubConfig /// Gets or sets . /// public string LaunchResourceEndpoint { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string ContentServerUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public int EmailConfirmationTokenExpiryMinutes { get; set; } = 0; + + /// + /// Gets or sets . + /// + public int LearningHubTenantId { get; set; } = 0; + + /// + /// Gets or sets . + /// + public string SupportPages { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string SupportForm { get; set; } = null!; + + /// + /// Gets or sets a value indicating whether . + /// + public bool UseRedisCache { get; set; } = false; + + /// + /// Gets or sets . + /// + public string HierarchyEditPublishQueueName { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string ResourcePublishQueueRouteName { 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 440fcb5e7..f34790d51 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.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionAnswerRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionAnswerRepository.cs new file mode 100644 index 000000000..30a798b72 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionAnswerRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The AssessmentResourceActivityInteractionAnswer interface. + /// + public interface IAssessmentResourceActivityInteractionAnswerRepository : IGenericRepository + { + /// + /// Get Assessment Resource Activity Interaction Answer By Id. + /// + /// The id. + /// AssessmentResourceActivityInteractionAnswer. + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionRepository.cs new file mode 100644 index 000000000..5d9f8c175 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityInteractionRepository.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The AssessmentResourceActivityInteraction interface. + /// + public interface IAssessmentResourceActivityInteractionRepository : IGenericRepository + { + /// + /// Get Assessment Resource Activity Interaction By Id. + /// + /// The id. + /// AssessmentResourceActivityInteraction. + Task GetByIdAsync(int id); + + /// + /// Get the assessment resource activity interaction for the given user, activity, and question block. + /// + /// The user id. + /// The assessment resource activity id. + /// The question block id. + /// AssessmentResourceActivityInteraction. + Task GetInteractionForQuestion(int userId, int assessmentResourceActivityId, int questionBlockId); + + /// + /// Creates an assessment activity interaction. + /// + /// The user id. + /// The interaction. + /// The task. + Task CreateInteraction(int userId, AssessmentResourceActivityInteraction interaction); + + /// + /// Gets all the interactions for a given assessment resource activity. + /// + /// The assessment resource activity id. + /// The list of interactions. + Task> GetInteractionsForAssessmentResourceActivity(int assessmentResourceActivityId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityMatchQuestionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityMatchQuestionRepository.cs new file mode 100644 index 000000000..e08ecf2f9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityMatchQuestionRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Collections.Generic; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The IAssessmentResourceActivityMatchQuestionRepository interface. + /// + public interface IAssessmentResourceActivityMatchQuestionRepository : IGenericRepository + { + /// + /// Get Assessment Resource Activity by Assessment Resource Activity Id. + /// + /// The assessment resource activity id. + /// The Assessment Resource Activity. + IEnumerable GetByAssessmentResourceActivityIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityRepository.cs new file mode 100644 index 000000000..ae7e74153 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IAssessmentResourceActivityRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The AssessmentResourceActivity interface. + /// + public interface IAssessmentResourceActivityRepository : IGenericRepository + { + /// + /// Get Assessment Resource Activity By Id. + /// + /// The id. + /// AssessmentResourceActivity. + Task GetByIdAsync(int id); + + /// + /// Gets the latest assessment resource activity for the given resource version id and user id. + /// + /// The resource version id. + /// The user id. + /// The assessment resource activity task. + Task GetLatestAssessmentResourceActivity(int resourceVersionId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityInteractionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityInteractionRepository.cs new file mode 100644 index 000000000..29c4faa12 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityInteractionRepository.cs @@ -0,0 +1,27 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The MediaResourceActivityInteraction interface. + /// + public interface IMediaResourceActivityInteractionRepository : IGenericRepository + { + /// + /// Get Media Resource Activity Interaction By Id. + /// + /// The id. + /// MediaResourceActivityInteraction. + Task GetByIdAsync(int id); + + /// + /// Performs the analysis of media resource activity to populate the played segment data. + /// + /// The user id. + /// The resource version id. + /// The mediaResourceActivityId. + /// The . + Task CalculatePlayedMediaSegments(int userId, int resourceVersionId, int mediaResourceActivityId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityRepository.cs new file mode 100644 index 000000000..2dd227402 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IMediaResourceActivityRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The MediaResourceActivity interface. + /// + public interface IMediaResourceActivityRepository : IGenericRepository + { + /// + /// Get Media Resource Activity By Id. + /// + /// The id. + /// MediaResourceActivity. + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/INodeActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/INodeActivityRepository.cs new file mode 100644 index 000000000..3c16ea9af --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/INodeActivityRepository.cs @@ -0,0 +1,14 @@ +// +// Copyright (c) NHS England. +// +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// INodeActivityRepository. + /// + public interface INodeActivityRepository : IGenericRepository + { + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs new file mode 100644 index 000000000..0c778605c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IResourceActivityRepository.cs @@ -0,0 +1,108 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.MyLearning; + + /// + /// The ResourceActivity interface. + /// + public interface IResourceActivityRepository : IGenericRepository + { + /// + /// Get Resource Activity By Id. + /// + /// The id. + /// ResourceActivity. + Task GetByIdAsync(int id); + + /// + /// Create activity record against a ResourceVersion. + /// + /// The user id. + /// The resource version id. + /// The node path id. + /// The launch resource activity id. + /// The activity Status Enum. + /// The activity Start. + /// The activity End. + /// Activity Id. + int CreateActivity( + int userId, + int resourceVersionId, + int nodePathId, + int? launchResourceActivityId, + ActivityStatusEnum activityStatusEnum, + DateTimeOffset? activityStart, + DateTimeOffset? activityEnd); + + /// + /// Get Resource Activity By user id. + /// + /// The user id. + /// ResourceActivity. + IQueryable GetByUserId(int userId); + + /// + /// Gets a list of incomplete media activities. Those that for any reason, the end of the user's activity was not recorded normally. For example - browser crash, power loss, connection loss. + /// + /// The user id. + /// The . + Task> GetIncompleteMediaActivities(int userId); + + /// + /// Gets a list of all the user's activities for a given resource. + /// + /// The user id.> + /// The resource id. + /// The . + Task> GetAllTheActivitiesFor(int userId, int resourceId); + + /// + /// Check if scorm activity has been completed. + /// + /// The user id. + /// The scormActivityId id. + /// The . + Task IsScormActivityFinished(int userId, int scormActivityId); + + /// + /// Get Resource Activity By user id. + /// + /// The user id. + /// requestModel. + /// detailedMediaActivityRecordingStartDate. + /// ResourceActivity. + Task> GetByUserIdFromSP(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate); + + /// + /// Check if scorm activity has been completed. + /// + /// The user id. + /// requestModel. + /// detailedMediaActivityRecordingStartDate. + /// The . + int GetTotalCount(int userId, MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate); + + /// + /// Gets a list of all the user's activities for a given resource. + /// + /// The user id.> + /// The resource id. + /// The . + Task> GetAllTheActivitiesFromSP(int userId, int resourceId); + + /// + /// Get the assessment activity completion details. + /// + /// The userId. + /// The resourceVersionId. + /// The activityId. + /// The . + Task GetAssessmentActivityCompletionPercentage(int userId, int resourceVersionId, int activityId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IScormActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IScormActivityRepository.cs new file mode 100644 index 000000000..19757c6d8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Activity/IScormActivityRepository.cs @@ -0,0 +1,66 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Dto; + using LearningHub.Nhs.Models.Entities.Activity; + + /// + /// The Scorm Activity interface. + /// + public interface IScormActivityRepository : IGenericRepository + { + /// + /// Get Scorm Activity By Id. + /// + /// The id. + /// Scorm Activity. + Task GetByIdAsync(int id); + + /// + /// The create activity. + /// + /// The user id. + /// The resource version id. + /// Scorm Activity Id. + int Create(int userId, int resourceVersionId); + + /// + /// Complete scorm activity. + /// Returns the resource activity id of the completion event. + /// + /// The user id. + /// The scorm activity id. + /// Resource Activity Id. + int Complete(int userId, int scormActivityId); + + /// + /// Resolve scorm activity. + /// + /// The scorm activity id. + void Resolve(int scormActivityId); + + /// + /// Gets the previously launched incomplete scrom activity id. + /// + /// userId. + /// resourceReferenceId. + /// ScormActivitySummaryDto. + ScormActivitySummaryDto GetScormActivitySummary(int userId, int resourceReferenceId); + + /// + /// Clones the incomplete scrom activity session with the newly created session. + /// + /// incompleteScromActivityId. + /// scromActivityId. + /// ScormActivity. + ScormActivity Clone(int incompleteScromActivityId, int scromActivityId); + + /// + /// Check user scorm activity data suspend data need to be cleared. + /// + /// last scorm activity id. + /// resource version id. + /// boolean. + Task CheckUserScormActivitySuspendDataToBeCleared(int lastScormActivityId, int resourceVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueAccessRequestRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueAccessRequestRepository.cs new file mode 100644 index 000000000..05fa86695 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueAccessRequestRepository.cs @@ -0,0 +1,46 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + + /// + /// The ICatalogueAccessRequestRepository interface. + /// + public interface ICatalogueAccessRequestRepository : IGenericRepository + { + /// + /// The GetByUserIdAndCatalogueId. + /// + /// The catalogueId. + /// The userId. + /// The catalogueAccessRequest. + CatalogueAccessRequest GetByUserIdAndCatalogueId(int catalogueNodeId, int userId); + + /// + /// The GetAllByUserIdAndCatalogueId. + /// + /// The catalogueId. + /// The userId. + /// The catalogueAccessRequest. + IQueryable GetAllByUserIdAndCatalogueId(int catalogueNodeId, int userId); + + /// + /// The CreateCatalogueAccessRequestAsync. + /// + /// The currentUserId. + /// The reference. + /// The message. + /// The roleId. + /// The catalogueManageAccessUrl. + /// The accessType. + /// The task. + Task CreateCatalogueAccessRequestAsync( + int currentUserId, + string reference, + string message, + int roleId, + string catalogueManageAccessUrl, + string accessType); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs new file mode 100644 index 000000000..e1137c520 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/ICatalogueNodeVersionRepository.cs @@ -0,0 +1,141 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Entities.Hierarchy; + + /// + /// The ICatalogueNodeVersionRepository. + /// + public interface ICatalogueNodeVersionRepository : IGenericRepository + { + /// + /// The get catalogues. + /// + /// The catalogue ids. + /// A representing the result of the asynchronous operation. + Task> GetCatalogues(List catalogueIds); + + /// + /// The get catalogues. + /// + /// The . + Task> GetPublishedCatalogues(); + + /// + /// The get catalogues for user. + /// + /// The userId. + /// The . + IQueryable GetPublishedCataloguesForUserAsync(int userId); + + /// + /// The get basic catalogue. + /// + /// The catalogueNodeId. + /// The . + CatalogueNodeVersion GetBasicCatalogue(int catalogueNodeId); + + /// + /// The UpdateCatalogueAsync. + /// + /// The userId. + /// The catalogue view model. + /// The task. + Task UpdateCatalogueAsync(int userId, CatalogueViewModel vm); + + /// + /// The UpdateCatalogueOwnerAsync. + /// + /// The userId. + /// The catalogue owner view model. + /// The task. + Task UpdateCatalogueOwnerAsync(int userId, CatalogueOwnerViewModel vm); + + /// + /// The CreateCatalogueAsync. + /// + /// The userId. + /// The catalogue view model. + /// The catalogueNodeVersionId. + Task CreateCatalogueAsync(int userId, CatalogueViewModel vm); + + /// + /// Get Catlogue by reference. + /// + /// The reference. + /// The CatalogueViewModel. + Task GetCatalogueAsync(string reference); + + /// + /// Get list of Restricted Catalogue AccessRequests for the supplied request. + /// + /// The restrictedCatalogueAccessRequestsRequestViewModel. + /// A RestrictedCatalogueAccessRequestsViewModel. + List GetRestrictedCatalogueAccessRequests(RestrictedCatalogueAccessRequestsRequestViewModel restrictedCatalogueAccessRequestsRequestViewModel); + + /// + /// Get list of RestrictedCatalogueUsersRequestViewModel for the supplied request. + /// + /// The restrictedCatalogueUsersRequestViewModel. + /// A RestrictedCatalogueUsersViewModel. + RestrictedCatalogueUsersViewModel GetRestrictedCatalogueUsers(RestrictedCatalogueUsersRequestViewModel restrictedCatalogueUsersRequestViewModel); + + /// + /// The ShowCatalogue. + /// + /// The UserId. + /// The NodeId. + /// The Task. + Task ShowCatalogue(int userId, int nodeId); + + /// + /// Get Restricted Catalogue Summary for the supplied catalogue node id. + /// + /// The catalogueNodeId. + /// A RestrictedCatalogueUsersViewModel. + RestrictedCatalogueSummaryViewModel GetRestrictedCatalogueSummary(int catalogueNodeId); + + /// + /// Gets catalogues for dashboard based on type. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// Catalogue totals and records. + (int TotalCount, List Catalogues) GetCatalogues(string dashboardType, int pageNumber, int userId); + + /// + /// Check if a Catalogue with a specific name exists or not. + /// + /// The catalogue name. + /// True if the catalogue exists, otherwise false. + Task ExistsAsync(string name); + + /// + /// Gets the Node Id for a particular catalogue name. + /// + /// The catalogue name. + /// The catalogue's node id. + Task GetNodeIdByCatalogueName(string catalogueName); + + /// + /// Gets the catalogues count in alphabet list. + /// + /// The userId. + /// The catalogues alphabet count list. + List GetAllCataloguesAlphaCount(int userId); + + /// + /// Gets catalogues based on filter character. + /// + /// The pageSize. + /// The filterChar. + /// The userId. + /// The catalogues. + Task> GetAllCataloguesAsync(int pageSize, string filterChar, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IFolderNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IFolderNodeVersionRepository.cs new file mode 100644 index 000000000..2facce74e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IFolderNodeVersionRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + + /// + /// The IFolderNodeVersionRepository. + /// + public interface IFolderNodeVersionRepository : IGenericRepository + { + /// + /// The GetFolder. + /// + /// The node version id. + /// The folder node version. + Task GetFolderAsync(int nodeVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditDetailRepository.cs new file mode 100644 index 000000000..ae8f37969 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditDetailRepository.cs @@ -0,0 +1,19 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + + /// + /// The HierarchyEditDetailRepository interface. + /// + public interface IHierarchyEditDetailRepository : IGenericRepository + { + /// + /// The get root hierarchy detail by hierarchy edit id async. + /// + /// The hierarchy edit id. + /// The node id. + /// The . + Task GetByNodeIdAsync(long hierarchyEditId, int nodeId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditRepository.cs new file mode 100644 index 000000000..3337ab4e1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IHierarchyEditRepository.cs @@ -0,0 +1,177 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + + /// + /// The HierarchyEditRepository interface. + /// + public interface IHierarchyEditRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by root node id async. + /// + /// The id. + /// The . + Task> GetByRootNodeIdAsync(int rootNodeId); + + /// + /// The create. + /// + /// The root node id. + /// The user id. + /// The hierarchy edit id. + Task Create(int rootNodeId, int userId); + + /// + /// The discard. + /// + /// The root node id. + /// The user id. + /// The . + Task Discard(int hierarchyEditId, int userId); + + /// + /// Creates a new folder. + /// + /// The folderEditViewModel. + /// The user id. + /// The . + Task CreateFolder(FolderEditViewModel folderEditViewModel, int userId); + + /// + /// Updates a folder. + /// + /// The folderEditViewModel. + /// The user id. + /// The . + Task UpdateFolder(FolderEditViewModel folderEditViewModel, int userId); + + /// + /// Deletes a folder. + /// + /// The id. + /// The user id. + /// The . + Task DeleteFolder(int hierarchyEditDetailId, int userId); + + /// + /// Moves a node up. + /// + /// The id. + /// The user id. + /// The . + Task MoveNodeUp(int hierarchyEditDetailId, int userId); + + /// + /// Moves a node down. + /// + /// The id. + /// The user id. + /// The . + Task MoveNodeDown(int hierarchyEditDetailId, int userId); + + /// + /// Moves a node. + /// + /// The moveNodeViewModel . + /// The user id. + /// The . + Task MoveNode(MoveNodeViewModel moveNodeViewModel, int userId); + + /// + /// Moves a resource up in a hierarchy edit. + /// + /// The id. + /// The user id. + /// The . + Task HierarchyEditMoveResourceUp(int hierarchyEditDetailId, int userId); + + /// + /// Moves a resource down in a hierarchy edit. + /// + /// The id. + /// The user id. + /// The . + Task HierarchyEditMoveResourceDown(int hierarchyEditDetailId, int userId); + + /// + /// Moves a resource in a HierarchyEdit. + /// + /// The view model . + /// The user id. + /// The . + Task HierarchyEditMoveResource(HierarchyEditMoveResourceViewModel moveResourceViewModel, int userId); + + /// + /// Moves a resource up. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node containing the resource. + /// The resource id. + /// The user id. + /// The . + Task MoveResourceUp(int nodeId, int resourceId, int userId); + + /// + /// Moves a resource down. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node containing the resource. + /// The resource id. + /// The user id. + /// The . + Task MoveResourceDown(int nodeId, int resourceId, int userId); + + /// + /// Moves a resource. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node to move the resource from. + /// The id of the node to move the resource to. + /// The resource id. + /// The user id. + /// A list of nodeIds affected by the moved resource. The nodes that will need to be refreshed in the UI. + Task> MoveResource(int sourceNodeId, int destinationNodeId, int resourceId, int userId); + + /// + /// The publishing. + /// + /// The hierarchy edit id. + /// The user id. + void SubmitForPublishing(int hierarchyEditId, int userId); + + /// + /// The publish. + /// + /// The hierarchy edit id. + /// The is major revision. + /// The notes. + /// The user id. + /// The publication id. + int Publish(int hierarchyEditId, bool isMajorRevision, string notes, int userId); + + /// + /// Set hierarchy edit to "publishing". + /// + /// The hierarchy edit id. + /// The user id. + void Publishing(int hierarchyEditId, int userId); + + /// + /// Set hierarchy edit to "failed to publish". + /// + /// The hierarchy edit id. + /// The user id. + void FailedToPublish(int hierarchyEditId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodePathRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodePathRepository.cs new file mode 100644 index 000000000..a24b8c6d3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodePathRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + + /// + /// The INodePathRepository. + /// + public interface INodePathRepository : IGenericRepository + { + /// + /// Gets the root catalogue nodeId for any given nodeId (i.e. folder/course). + /// + /// The folder/course nodeId. + /// The catalogue nodeId. + Task GetCatalogueRootNodeId(int folderNodeId); + + /// + /// Gets the node paths to the supplied node id. + /// + /// The nodeId. + /// The list of NodePaths. + Task> GetNodePathsForNodeId(int nodeId); + + /// + /// Gets the basic details of all Nodes in a particular NodePath. + /// + /// The NodePath id. + /// The . + Task> GetNodePathNodes(int nodePathId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeRepository.cs new file mode 100644 index 000000000..297be93f1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeRepository.cs @@ -0,0 +1,53 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + + /// + /// The NodeRepository interface. + /// + public interface INodeRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// Gets the basic details of a node. Currently catalogues or folders. + /// + /// The node id. + /// The node details. + Task GetNodeDetails(int nodeId); + + /// + /// Gets the contents of a node for the catalogue landing page - i.e. published folders and published resources only. + /// Only returns the items found directly in the specified node, does not recurse down through subfolders. + /// + /// The node id. + /// Include Empty Folder or not. + /// The . + Task> GetNodeContentsForCatalogueBrowse(int nodeId, bool includeEmptyFolder); + + /// + /// Gets the contents of a node for the My Contributions page - i.e. published folders only, and all resources (i.e. all statuses). + /// Only returns the items found directly in the specified node, does not recurse down through subfolders. + /// + /// The node id. + /// The . + Task> GetNodeContentsForCatalogueEditor(int nodeId); + + /// + /// Gets the contents of a node (catalogue/folder/course) - i.e. returns a list of subfolders and resources. Only returns the + /// items from the first level down. Doesn't recurse through subfolders. + /// + /// The node id. + /// Set to true if read only data set is required. + /// The . + Task> GetNodeContentsAdminAsync(int nodeId, bool readOnly); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceLookupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceLookupRepository.cs new file mode 100644 index 000000000..733b1e7eb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceLookupRepository.cs @@ -0,0 +1,19 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + + /// + /// The INodeResourceLookupRepository interface. + /// + public interface INodeResourceLookupRepository : IGenericRepository + { + /// + /// The get by node id async. + /// + /// The node id. + /// The . + Task> GetByNodeIdAsync(int nodeId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceRepository.cs new file mode 100644 index 000000000..dce087b47 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/INodeResourceRepository.cs @@ -0,0 +1,53 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Hierarchy; + + /// + /// The NodeResourceRepository interface. + /// + public interface INodeResourceRepository : IGenericRepository + { + /// + /// The get by resource id async. + /// + /// The resource id. + /// The . + Task> GetByResourceIdAsync(int resourceId); + + /// + /// The get catalogue locations for resource. + /// + /// The resource id. + /// The CatalogueLocationViewModel list. + List GetCatalogueLocationsForResource(int resourceId); + + /// + /// GetResourcesAsync. + /// + /// nodeId. + /// catalogueOrder. + /// offset. + /// A representing the result of the asynchronous operation. + Task GetResourcesAsync(int nodeId, CatalogueOrder catalogueOrder, int offset); + + /// + /// Get All published resources id. + /// + /// The . + Task> GetAllPublishedResourceAsync(); + + /// + /// Creates or updates the NodeResource record for a draft resource in a node. + /// + /// The nodeId. + /// The resourceId. + /// The userId. + /// The . + Task CreateOrUpdateAsync(int nodeId, int resourceId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IPublicationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IPublicationRepository.cs new file mode 100644 index 000000000..ee6743a0e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Hierarchy/IPublicationRepository.cs @@ -0,0 +1,48 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + + /// + /// The PublicationRepository interface. + /// + public interface IPublicationRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by id. + /// + /// The id. + /// The . + Publication GetById(int id); + + /// + /// The get by resourceVersionId. + /// + /// The resourceVersionId. + /// The Publication. + Task GetByResourceVersionIdAsync(int resourceVersionId); + + /// + /// Get cache operations for the supplied publication id. + /// + /// The publicationId. + /// A list of . + Task> GetCacheOperations(int publicationId); + + /// + /// Get resource first publication record. + /// + /// resource id. + /// publish view model. + Task GetResourceFirstPublication(int resourceId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IBookmarkRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IBookmarkRepository.cs new file mode 100644 index 000000000..35fefbefe --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IBookmarkRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The IRoadmapRepository. + /// + public interface IBookmarkRepository : IGenericRepository + { + /// + /// The GetById. + /// + /// The bookmarkId. + /// The . + Task GetById(int bookmarkId); + + /// + /// DeleteFolder. + /// + /// bookmarkId. + /// userId. + /// A representing the result of the asynchronous operation. + Task DeleteFolder(int bookmarkId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEmailChangeValidationTokenRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEmailChangeValidationTokenRepository.cs new file mode 100644 index 000000000..390f1c39a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IEmailChangeValidationTokenRepository.cs @@ -0,0 +1,72 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The EmailChangeValidationTokenRepository interface. + /// + public interface IEmailChangeValidationTokenRepository + { + /// + /// The get by token. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + Task GetByToken(string lookup); + + /// + /// The GetLastIssuedEmailChangeValidationToken. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + Task GetLastIssuedEmailChangeValidationToken(int userId); + + /// + /// The expire email change validation token. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + Task ExpireEmailChangeValidationToken(string lookup); + + /// + /// The create async. + /// + /// + /// The user id. + /// + /// + /// The email change validation token. + /// + /// + /// The . + /// + Task CreateAsync(int userId, EmailChangeValidationToken emailChangeValidationToken); + + /// + /// The create async. + /// + /// + /// The user id. + /// + /// + /// The email change validation token. + /// + /// + /// The . + /// + Task UpdateAsync(int userId, EmailChangeValidationToken emailChangeValidationToken); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IGenericRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IGenericRepository.cs new file mode 100644 index 000000000..25969ea32 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IGenericRepository.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The GenericRepository interface. + /// + /// Imput type. + public interface IGenericRepository + where TEntity : EntityBase + { + /// + /// The get all. + /// + /// The . + IQueryable GetAll(); + + /// + /// The create async. + /// + /// The user id. + /// The entity. + /// The . + Task CreateAsync(int userId, TEntity entity); + + /// + /// The update async. + /// + /// The user id. + /// The entity. + /// The . + Task UpdateAsync(int userId, TEntity entity); + + /// + /// The update. + /// + /// The user id. + /// The entity. + void Update(int userId, TEntity entity); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationTemplateRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationTemplateRepository.cs new file mode 100644 index 000000000..b434a2095 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationTemplateRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + + /// + /// The INotificationTemplateRepository interface. + /// + public interface INotificationTemplateRepository : IGenericRepository + { + /// + /// The GetById. + /// + /// The templateId. + /// The notification template. + NotificationTemplate GetById(NotificationTemplates templateId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IProviderRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IProviderRepository.cs new file mode 100644 index 000000000..3e3e5ed7d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IProviderRepository.cs @@ -0,0 +1,48 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The ProviderRepository interface. + /// + public interface IProviderRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by id async. + /// + /// The id. + /// The include children. + /// The . + Task GetByIdAsync(int id, bool includeChildren); + + /// + /// The get by user id async. + /// + /// The user id. + /// The . + IQueryable GetProvidersByUserIdAsync(int userId); + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + IQueryable GetProvidersByResourceIdAsync(int resourceVersionId); + + /// + /// The get by node version id async. + /// + /// The node version id. + /// The . + IQueryable GetProvidersByCatalogueIdAsync(int nodeVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs index 52b3dee08..1b9d45411 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs @@ -4,25 +4,26 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories using System.Threading.Tasks; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; /// /// Resource repository interface. /// - public interface IResourceRepository + public interface IResourceRepository : IGenericRepository { /// /// Gets resources from ids. /// /// . /// Resources with details. - public Task> GetResourcesFromIds(IEnumerable resourceIds); + Task> GetResourcesFromIds(IEnumerable resourceIds); /// /// Gets resource references by their original resource reference Ids. /// /// . /// Resource references. - public Task> GetResourceReferencesByOriginalResourceReferenceIds( + Task> GetResourceReferencesByOriginalResourceReferenceIds( IEnumerable originalResourceReferenceIds); /// @@ -37,6 +38,39 @@ public Task> GetResourceReferencesByOriginalResou /// GetAchievedCertificatedResourceIds /// /// . - public Task> GetAchievedCertificatedResourceIds(int currentUserId); + Task> GetAchievedCertificatedResourceIds(int currentUserId); + + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + + /// + /// Returns true if the user has any resources published. + /// + /// The user id. + /// If the user has any resources published. + Task UserHasPublishedResourcesAsync(int userId); + + /// + /// The create resource async. + /// + /// The resource type. + /// The title. + /// The description. + /// The user id. + /// The . + Task CreateResourceAsync(ResourceTypeEnum resourceType, string title, string description, int userId); + + /// + /// The get by resourve version id async. + /// + /// The resource version id. + /// The . + Task GetByResourceVersionIdAsync(int resourceVersionId); + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IRoleUserGroupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IRoleUserGroupRepository.cs new file mode 100644 index 000000000..7fd1426a1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IRoleUserGroupRepository.cs @@ -0,0 +1,67 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.User; + + /// + /// The IRoleUserGroupRepository interface. + /// + public interface IRoleUserGroupRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by catalogueNodeId async. + /// + /// The role id. + /// The userGroup id. + /// The scope id. + /// The . + Task GetByRoleIdUserGroupIdScopeIdAsync(int roleId, int userGroupId, int scopeId); + + /// + /// The get by role id and catalogue id. + /// + /// The role id. + /// The catalogue node id. + /// The . + Task> GetByRoleIdCatalogueId(int roleId, int catalogueNodeId); + + /// + /// The get by role id and catalogue id that has users. + /// + /// The role id. + /// The catalogue node id. + /// The . + Task> GetByRoleIdCatalogueIdWithUsers(int roleId, int catalogueNodeId); + + /// + /// Get list of RoleUserGroupViewModel for a supplied User Group. + /// + /// The userGroupId. + /// A list of RoleUserGroupViewModel. + Task> GetRoleUserGroupViewModelsByUserGroupId(int userGroupId); + + /// + /// Get list of RoleUserGroupViewModel for a supplied User Group. + /// + /// The userGroupId. + /// A list of RoleUserGroupViewModel. + Task> GetRoleUserGroupViewModelsByUserId(int userId); + + /// + /// The get all for search. + /// + /// The catalogueNodeId. + /// The userId. + /// The . + Task> GetAllforSearch(int catalogueNodeId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/ITimezoneOffsetManager.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/ITimezoneOffsetManager.cs new file mode 100644 index 000000000..15f4b8bf1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/ITimezoneOffsetManager.cs @@ -0,0 +1,22 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System; + + /// + /// The TimezoneOffsetManager interface. + /// + public interface ITimezoneOffsetManager + { + /// + /// Gets User Timezone Offset. + /// + int? UserTimezoneOffset { get; } + + /// + /// Converts a UTC DateTimeOffset to the timezone of the current user. + /// + /// The UTC DateTimeOffset to convert. + /// . + DateTimeOffset ConvertToUserTimezone(DateTimeOffset utcDate); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProfileRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProfileRepository.cs new file mode 100644 index 000000000..1611e99b3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserProfileRepository.cs @@ -0,0 +1,32 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The UserProfileRepository interface. + /// + public interface IUserProfileRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The GetByEmailAddressAsync. + /// + /// The email address. + /// The userProfile. + Task GetByEmailAddressAsync(string emailAddress); + + /// + /// The GetByUsernameAsync. + /// + /// The userName. + /// The userProfile. + Task GetByUsernameAsync(string userName); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserRepository.cs new file mode 100644 index 000000000..ff8b6cb5b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserRepository.cs @@ -0,0 +1,40 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The UserRepository interface. + /// + public interface IUserRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by id include roles async. + /// + /// The id. + /// The . + Task GetByIdIncludeRolesAsync(int id); + + /// + /// The get by username async. + /// + /// The username. + /// The include roles. + /// The . + Task GetByUsernameAsync(string username, bool includeRoles); + + /// + /// Returns indication of whether the user in an Admin. + /// + /// The user id. + /// The . + bool IsAdminUser(int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserUserGroupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserUserGroupRepository.cs new file mode 100644 index 000000000..ee39c0e79 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserUserGroupRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The UserUserGroupRepository interface. + /// + public interface IUserUserGroupRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// to get the user user group details by user id and usergroup id. + /// + /// userId. + /// userGroupId. + /// The . + Task GetByUserIdandUserGroupIdAsync(int userId, int userGroupId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Maintenance/IInternalSystemRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Maintenance/IInternalSystemRepository.cs new file mode 100644 index 000000000..e5b4b6304 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Maintenance/IInternalSystemRepository.cs @@ -0,0 +1,11 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Maintenance +{ + using LearningHub.Nhs.Models.Entities.Maintenance; + + /// + /// The InternalSystemRepository. + /// + public interface IInternalSystemRepository : IGenericRepository + { + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IEmailTemplateRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IEmailTemplateRepository.cs new file mode 100644 index 000000000..2ea140c2d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IEmailTemplateRepository.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Messaging +{ + using LearningHub.Nhs.Models.Entities.Messaging; + + /// + /// The IEmailTemplateRepository. + /// + public interface IEmailTemplateRepository + { + /// + /// The GetTemplate. + /// + /// The email template id. + /// The Email Template. + EmailTemplate GetTemplate(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IMessageRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IMessageRepository.cs new file mode 100644 index 000000000..67ed87b2a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Messaging/IMessageRepository.cs @@ -0,0 +1,78 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Messaging +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Messaging; + + /// + /// The IMessageRepository class. + /// + public interface IMessageRepository + { + /// + /// Gets a list of all messages which have a message send which hasn't been sent. + /// + /// The messages. + IQueryable GetPendingMessages(); + + /// + /// Creates an email to be sent. + /// + /// The user id. + /// The subject. + /// The body. + /// The recipient user id. + /// The task. + Task CreateEmailAsync(int userId, string subject, string body, int recipientUserId); + + /// + /// The CreateEmailAsync. + /// + /// The user id. + /// The subject. + /// The body. + /// The recipientEmailAddress. + /// The task. + Task CreateEmailAsync(int userId, string subject, string body, string recipientEmailAddress); + + /// + /// The CreateNotificationForUserAsync. + /// + /// The userId. + /// The subject. + /// The body. + /// The recipientUserId. + /// The notificationStartDate. + /// The notificationEndDate. + /// The notificationPriority. + /// The notificationType. + /// The task. + Task CreateNotificationForUserAsync( + int userId, + string subject, + string body, + int recipientUserId, + DateTimeOffset notificationStartDate, + DateTimeOffset notificationEndDate, + int notificationPriority, + int notificationType); + + /// + /// Marks a message send as having been successful. + /// + /// The userId. + /// The messageSends. + /// The task. + Task MessageSendSuccess(int userId, List messageSends); + + /// + /// Either marks a message as failed, or queues it for a retry. + /// + /// The userId. + /// The messageSends. + /// The task. + Task MessageSendFailure(int userId, List messageSends); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Migrations/IMigrationSourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Migrations/IMigrationSourceRepository.cs new file mode 100644 index 000000000..f12fe6179 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Migrations/IMigrationSourceRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Migrations +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Migration; + + /// + /// The MigrationSourceRepository interface. + /// + public interface IMigrationSourceRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionFileRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionFileRepository.cs new file mode 100644 index 000000000..6907ae321 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionFileRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ArticleResourceVersionFileRepository interface. + /// + public interface IArticleResourceVersionFileRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by id async. + /// + /// The resourceVersionId. + /// The fileId. + /// The . + Task GetByResourceVersionAndFileAsync(int resourceVersionId, int fileId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionRepository.cs new file mode 100644 index 000000000..048fe9f4d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IArticleResourceVersionRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ArticleResourceVersionRepository interface. + /// + public interface IArticleResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAssessmentResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAssessmentResourceVersionRepository.cs new file mode 100644 index 000000000..028800c44 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAssessmentResourceVersionRepository.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The AssessmentResourceVersionRepository interface. + /// + public interface IAssessmentResourceVersionRepository : IGenericRepository + { + /// + /// The get by Assessment Version Id async. + /// + /// The Assessment Version Id. + /// The . + Task GetByResourceVersionIdAsync(int assessmentVersionId); + + /// + /// The get by Assessment Content Id async. + /// + /// The Assessment Content Id. + /// The . + Task GetByAssessmentContentBlockCollectionIdAsync(int assessmentContentId); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAudioResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAudioResourceVersionRepository.cs new file mode 100644 index 000000000..9d1690217 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IAudioResourceVersionRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The AudioResourceVersionRepository interface. + /// + public interface IAudioResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IBlockCollectionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IBlockCollectionRepository.cs new file mode 100644 index 000000000..cadadd20b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IBlockCollectionRepository.cs @@ -0,0 +1,43 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + using LearningHub.Nhs.Models.Enums; + + /// + /// The BlockRepository interface. + /// + public interface IBlockCollectionRepository : IGenericRepository + { + /// + /// Delete the Block Collection. + /// + /// The User Id. + /// The Block Collection Id. + /// The . + Task DeleteBlockCollection(int userId, int blockCollectionId); + + /// + /// Gets the Block Collection. + /// + /// The Block Collection Id. + /// The . + Task GetBlockCollection(int? blockCollectionId); + + /// + /// Gets the Question blocks for a particular blockCollectionId. + /// + /// The Block Collection Id. + /// The . + Task> GetQuestionBlocks(int blockCollectionId); + + /// + /// Gets the Case, AssessmentContent, AssessmentGuidance Block Collections (including child Blocks, TextBlocks, WholeSlideImageBlocks and Files) except that of the provided resource version. + /// + /// The excluded ResourceVersion Id. + /// The resource type. + /// The . + Task GetResourceBlockCollectionsFileAsync(int excludeResourceVersionId, ResourceTypeEnum resourceTypeEnum); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/ICaseResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/ICaseResourceVersionRepository.cs new file mode 100644 index 000000000..af9c600bc --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/ICaseResourceVersionRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The CaseResourceVersionRepository interface. + /// + public interface ICaseResourceVersionRepository : IGenericRepository + { + /// + /// The get by Resource Version Id async. + /// + /// The Resource Version Id. + /// The . + Task GetByResourceVersionIdAsync(int resourceVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEmbeddedResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEmbeddedResourceVersionRepository.cs new file mode 100644 index 000000000..b35680f24 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEmbeddedResourceVersionRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The EmbeddedResourceVersionRepository interface. + /// + public interface IEmbeddedResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEquipmentResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEquipmentResourceVersionRepository.cs new file mode 100644 index 000000000..1f9807c7c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IEquipmentResourceVersionRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The EquipmentResourceVersionRepository interface. + /// + public interface IEquipmentResourceVersionRepository : IGenericRepository + { + /// + /// The get by resource version id async. + /// + /// The id. + /// The . + Task GetByResourceVersionIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileChunkDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileChunkDetailRepository.cs new file mode 100644 index 000000000..4f6b7dea4 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileChunkDetailRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The FileChunkDetailRepository interface. + /// + public interface IFileChunkDetailRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// Delete a file chunk detail. + /// + /// The file Chunk Detail id. + /// The user id. + /// The . + Task Delete(int fileChunkDetailId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileRepository.cs new file mode 100644 index 000000000..7baf4ed9d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The FileRepository interface. + /// + public interface IFileRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileTypeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileTypeRepository.cs new file mode 100644 index 000000000..33ca0ec23 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IFileTypeRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The FileTypeRepository interface. + /// + public interface IFileTypeRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IGenericFileResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IGenericFileResourceVersionRepository.cs new file mode 100644 index 000000000..28eabdede --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IGenericFileResourceVersionRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The GenericFileResourceVersionRepository interface. + /// + public interface IGenericFileResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + + /// + /// The get by resource version id. + /// + /// The id. + /// Allows deleted items to be returned. + /// The generic file resource version. + GenericFileResourceVersion GetByResourceVersionId(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IHtmlResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IHtmlResourceVersionRepository.cs new file mode 100644 index 000000000..3def3f0b9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IHtmlResourceVersionRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The GenericFileResourceVersionRepository interface. + /// + public interface IHtmlResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + + /// + /// The get by resource version id. + /// + /// The id. + /// Allows deleted items to be returned. + /// The html resource version. + HtmlResourceVersion GetByResourceVersionId(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IImageResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IImageResourceVersionRepository.cs new file mode 100644 index 000000000..1a2af621e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IImageResourceVersionRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ImageResourceVersionRepository interface. + /// + public interface IImageResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IQuestionBlockRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IQuestionBlockRepository.cs new file mode 100644 index 000000000..ea88cc732 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IQuestionBlockRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + + /// + /// The QuestionBlockRepository interface. + /// + public interface IQuestionBlockRepository : IGenericRepository + { + /// + /// The get by Block Collection Id async. + /// + /// The block collection Id. + /// The . + Task GetByQuestionBlockCollectionIdAsync(int blockCollectionId); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceLicenceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceLicenceRepository.cs new file mode 100644 index 000000000..876227b27 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceLicenceRepository.cs @@ -0,0 +1,11 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The FileTypeRepository interface. + /// + public interface IResourceLicenceRepository : IGenericRepository + { + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceReferenceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceReferenceRepository.cs new file mode 100644 index 000000000..2042a5c9a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceReferenceRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceReferenceRepository interface. + /// + public interface IResourceReferenceRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The include NodePath. + /// The . + Task GetByIdAsync(int id, bool includeNodePath); + + /// + /// The get by original resource reference id async. + /// + /// The id. + /// The include NodePath. + /// The . + Task GetByOriginalResourceReferenceIdAsync(int id, bool includeNodePath); + + /// + /// The get default by resource id async. + /// + /// The resource id. + /// The . + Task GetDefaultByResourceIdAsync(int resourceId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionAuthorRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionAuthorRepository.cs new file mode 100644 index 000000000..e96c4cf09 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionAuthorRepository.cs @@ -0,0 +1,27 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionAuthorRepository interface. + /// + public interface IResourceVersionAuthorRepository : IGenericRepository + { + /// + /// The delete async. + /// + /// The user id. + /// The resource version author id. + /// The . + Task DeleteAsync(int userId, int resourceVersionAuthorId); + + /// + /// The delete async. + /// + /// The resource version id. + /// The author id. + /// The . + Task GetByResourceVersionAndAuthorAsync(int resourceVersionId, int authorId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionFlagRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionFlagRepository.cs new file mode 100644 index 000000000..ba4876732 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionFlagRepository.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Validation; + + /// + /// The ResourceVersionFlagRepository interface. + /// + public interface IResourceVersionFlagRepository : IGenericRepository + { + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + IQueryable GetByResourceVersionIdAsync(int resourceVersionId); + + /// + /// The update resource version flag async. + /// + /// The user id. + /// The resource version flag. + /// The . + Task UpdateResourceVersionFlagAsync(int userId, ResourceVersionFlag resourceVersionFlag); + + /// + /// The delete async. + /// + /// The user id. + /// The resource version flag id. + /// The . + Task DeleteAsync(int userId, int resourceVersionFlagId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionKeywordRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionKeywordRepository.cs new file mode 100644 index 000000000..78127d86f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionKeywordRepository.cs @@ -0,0 +1,35 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionKeywordRepository interface. + /// + public interface IResourceVersionKeywordRepository : IGenericRepository + { + /// + /// The delete async. + /// + /// The user id. + /// The resource version keyword id. + /// The . + Task DeleteAsync(int userId, int resourceVersionKeywordId); + + /// + /// The delete async. + /// + /// The resource version id. + /// The keyword id. + /// The . + Task GetByResourceVersionAndKeywordAsync(int resourceVersionId, int keywordId); + + /// + /// Check if a specific keyword exists for the Resource Version. + /// + /// The resource version id. + /// The keyword. + /// The . + Task DoesResourceVersionKeywordAlreadyExistAsync(int resourceVersionId, string keyword); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionProviderRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionProviderRepository.cs new file mode 100644 index 000000000..6a1bb065b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionProviderRepository.cs @@ -0,0 +1,28 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionEventRepository interface. + /// + public interface IResourceVersionProviderRepository : IGenericRepository + { + /// + /// Delete resource version provider. + /// + /// resource version id. + /// provider id. + /// user id. + /// The . + Task DeleteAsync(int resourceVersionId, int providerId, int userId); + + /// + /// Delete all resource version provider. + /// + /// resource version id. + /// user id. + /// The . + Task DeleteAllAsync(int resourceVersionId, int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRepository.cs new file mode 100644 index 000000000..b2e296682 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRepository.cs @@ -0,0 +1,303 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Resource.ResourceDisplay; + + /// + /// The ResourceVersionRepository interface. + /// + public interface IResourceVersionRepository : IGenericRepository + { + /// + /// The get all admin search. + /// + /// The userId. + /// The . + IQueryable GetAllAdminSearch(int userId); + + /// + /// The get by id async. + /// + /// The id. + /// The include events. + /// The . + Task GetByIdAsync(int id, bool includeEvents); + + /// + /// The get basic by id async. + /// + /// The id. + /// The . + Task GetBasicByIdAsync(int id); + + /// + /// The contribution totals. + /// + /// The user id. + /// The catalogue id. + /// The MyContributionsTotalsViewModel. + MyContributionsTotalsViewModel GetMyContributionTotals(int userId, int catalogueId); + + /// + /// Get resource cards. + /// + /// The include events. + /// The . + Task> GetResourceCards(bool includeEvents); + + /// + /// The get current for resource async. + /// + /// The resource Id. + /// The . + Task GetCurrentForResourceAsync(int resourceId); + + /// + /// The get current resource for resourceid async. + /// + /// The resource Id. + /// The . + Task GetCurrentResourceDetailsAsync(int resourceId); + + /// + /// The get resource version details by id async. + /// + /// The resourceVersionId. + /// The . + Task GetByResourceVersionByIdAsync(int resourceVersionId); + + /// + /// The check dev id already exists in the table async. + /// + /// The devId. + /// The . + Task DoesDevIdExistsAync(string devId); + + /// + /// The get current published for resource async. + /// + /// The resource id. + /// The . + Task GetCurrentPublicationForResourceAsync(int resourceId); + + /// + /// The get current for resource reference id async. + /// + /// The resource reference id. + /// The . + Task GetCurrentForResourceReferenceIdAsync(int resourceReferenceId); + + /// + /// The get current resource for resource reference id async. + /// + /// The resource reference id. + /// The . + Task GetCurrentResourceForResourceReferenceIdAsync(int resourceReferenceId); + + /// + /// The get current published for resource reference id async. + /// + /// The resource reference id. + /// The . + Task GetCurrentPublicationForResourceReferenceIdAsync(int resourceReferenceId); + + /// + /// The set resource type. + /// + /// The resource version id. + /// The resource type enum. + /// The user id. + void SetResourceType(int resourceVersionId, ResourceTypeEnum resourceTypeEnum, int userId); + + /// + /// Gets the resource type of a ResourceVersion. + /// + /// The resourceVersionId. + /// The . + Task GetResourceType(int resourceVersionId); + + /// + /// The publish. + /// + /// The resource version id. + /// The is major revision. + /// The notes. + /// The publication date. Set to null if not giving the resource a publication date in the past. This parameter is intended for use by the migration tool. + /// The user id. + /// The publication id. + int Publish(int resourceVersionId, bool isMajorRevision, string notes, DateTimeOffset? publicationDate, int userId); + + /// + /// The unpublish. + /// + /// The resource version id. + /// The details. + /// The user id. + void Unpublish(int resourceVersionId, string details, int userId); + + /// + /// The revert to draft. + /// + /// The resource version id. + /// The user id. + void RevertToDraft(int resourceVersionId, int userId); + + /// + /// Delete a resource version. + /// + /// The resource version id. + /// The user id. + void Delete(int resourceVersionId, int userId); + + /// + /// The get resource versions. + /// + /// The resource id. + /// The . + Task> GetResourceVersionsAsync(int resourceId); + + ///// + ///// The create activity. + ///// + ///// + ///// The user id. + ///// + ///// + ///// The resource version id. + ///// + ///// + ///// The activity status enum. + ///// + ///// + ///// The activity start. + ///// + ///// + ///// The activity end. + ///// + ////void CreateActivity( + // int userId, + // int resourceVersionId, + // ActivityStatusEnum activityStatusEnum, + // DateTimeOffset activityStart, + // DateTimeOffset activityEnd); + + /// + /// Check if a user has completed the activity corresponding to the resource version. + /// + /// The user id. + /// The resource version id. + /// A boolean. + Task HasUserCompletedActivity(int userId, int resourceVersionId); + + /// + /// Return whether a version at a specific status currently exists. + /// + /// The resource id. + /// The resource version status id. + /// Whether the resource version exists or not. + Task DoesVersionExist(int resourceId, VersionStatusEnum draft); + + /// + /// Return whether a version at a specific status currently exists. + /// + /// The resource id. + /// The user id. + /// Returns the new resource version id. + Task CreateNextVersionAsync(int resourceId, int userId); + + /// + /// Duplicates a resource version. + /// + /// The resource id. + /// The user id. + /// Returns the new resource version id. + Task CreateDuplicateVersionAsync(int resourceId, int userId); + + /// + /// The publishing. + /// + /// The resource version id. + /// The user id. + void Publishing(int resourceVersionId, int userId); + + /// + /// "Failed to publish". + /// + /// The resource version id. + /// The user id. + void FailedToPublish(int resourceVersionId, int userId); + + /// + /// The submit publishing. + /// + /// The resource version id. + /// The user id. + void SubmitForPublishing(int resourceVersionId, int userId); + + /// + /// Create resource version event. + /// + /// resourceVersionId. + /// resourceVersionEventType. + /// details. + /// user id. + void CreateResourceVersionEvent(int resourceVersionId, ResourceVersionEventTypeEnum resourceVersionEventType, string details, int userId); + + /// + /// Get Contributions. + /// + /// The userId. + /// The ResourceContributionsRequestViewModel. + /// A list of contributed resources. + List GetContributions(int userId, ResourceContributionsRequestViewModel resourceContributionsRequestViewModel); + + /// + /// The GetResourceVersionForIdList. + /// + /// List of resource version ids. + /// The resource version list. + Task> GetResourceVersionsForSearchSubmission(List resourceVersionIds); + + /// + /// Gets resources for dashboard based on type. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// resources. + (int resourceCount, List resources) GetResources(string dashboardType, int pageNumber, int userId); + + /// + /// Copy the blocks from source to destination. + /// + /// The user id. + /// The source block collection id. + /// The destination block collection id. + /// The blocks to be duplicated. + /// The . + Task FractionalDuplication(int userId, int sourceBlockCollectionId, int destinationBlockCollectionId, List blocks); + + /// + /// Gets external content details. + /// + /// resourceVersionId. + /// userId. + /// A representing the result of the asynchronous operation. + Task GetExternalContentDetails(int resourceVersionId, int userId); + + /// + /// To update dev id details. + /// + /// The userId. + /// The resourceVersionDevIdViewModel. + /// The . + Task UpdateDevIdAsync(int userId, ResourceVersionDevIdViewModel resourceVersionDevIdViewModel); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionUserAcceptanceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionUserAcceptanceRepository.cs new file mode 100644 index 000000000..4b0c1bc5a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionUserAcceptanceRepository.cs @@ -0,0 +1,11 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionUserAcceptanceRepository interface. + /// + public interface IResourceVersionUserAcceptanceRepository : IGenericRepository + { + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionValidationResultRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionValidationResultRepository.cs new file mode 100644 index 000000000..202df4ce1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionValidationResultRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The IResourceVersionValidationResultRepository interface. + /// + public interface IResourceVersionValidationResultRepository : IGenericRepository + { + /// + /// The GetByResourceVersionIdAsync. + /// + /// The resourceVersionId. + /// ResourceVersionValidationResult. + Task GetByResourceVersionIdAsync(int resourceVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IScormResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IScormResourceVersionRepository.cs new file mode 100644 index 000000000..2fefbe739 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IScormResourceVersionRepository.cs @@ -0,0 +1,50 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Resource; + + /// + /// The ScormResourceVersionRepository interface. + /// + public interface IScormResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// The includeDeleted. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + + /// + /// Gets the content details for a particular Learning Hub external reference (guid). + /// + /// The external reference (guid). + /// A ContentServerViewModel. + Task GetContentServerDetailsByLHExternalReference(string externalReference); + + /// + /// Gets the SCORM content details for a particular historic external URL. These historic URLs have to be supported to + /// allow historic ESR links on migrated resources to continue to work. + /// + /// The external Url. + /// A ContentServerViewModel. + Task GetScormContentServerDetailsByHistoricExternalUrl(string externalUrl); + + /// + /// GetExternalReferenceByResourceId. + /// + /// Resource id. + /// External Reference. + Task> GetExternalReferenceByResourceId(int resourceId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoRepository.cs new file mode 100644 index 000000000..26ebf43ad --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + + /// + /// The video repository interface. + /// + public interface IVideoRepository : IGenericRepository + { + /// + /// The get by file Id async. + /// + /// The file Id. + /// The . + Task GetByFileIdAsync(int fileId); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoResourceVersionRepository.cs new file mode 100644 index 000000000..374eff16d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IVideoResourceVersionRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The VideoResourceVersionRepository interface. + /// + public interface IVideoResourceVersionRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by resource version id async. + /// + /// The id. + /// Allows deleted items to be returned. + /// The . + Task GetByResourceVersionIdAsync(int id, bool includeDeleted = false); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWebLinkResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWebLinkResourceVersionRepository.cs new file mode 100644 index 000000000..7eea30ee3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWebLinkResourceVersionRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The WebLinkResourceVersionRepository interface. + /// + public interface IWebLinkResourceVersionRepository + { + /// + /// The get by resource version id async. + /// + /// The id. + /// The . + Task GetByResourceVersionIdAsync(int id); + + /// + /// The update async. + /// + /// The user id. + /// The web link resource version. + /// The . + Task UpdateAsync(int userId, WebLinkResourceVersion webLinkResourceVersion); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWholeSlideImageRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWholeSlideImageRepository.cs new file mode 100644 index 000000000..5482407d8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IWholeSlideImageRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + + /// + /// The whole slide image repository interface. + /// + public interface IWholeSlideImageRepository : IGenericRepository + { + /// + /// The get by fileId async. + /// + /// The file Id. + /// The . + Task GetByFileIdAsync(int fileId); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index c1ebf721b..559f982e5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -14,6 +14,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.Models.Entities.Resource.Blocks; using LearningHub.Nhs.Models.Hierarchy; using LearningHub.Nhs.Models.Messaging; + using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Resource.ResourceDisplay; using LearningHub.Nhs.Models.User; @@ -70,6 +71,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet RoleUserGroup { get; set; } + /// + /// Gets or sets the email change validation token. + /// + public virtual DbSet EmailChangeValidationToken { get; set; } + /// /// Gets or sets the User. /// @@ -165,6 +171,12 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet ResourceVersionFlag { get; set; } + + /// + /// Gets or sets the resource version validation result. + /// + public virtual DbSet ResourceVersionValidationResult { get; set; } + /// /// Gets or sets the resource version event.. /// @@ -300,6 +312,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet ResourceActivityDTO { get; set; } + /// + /// Gets or sets the scorm activity. + /// + public virtual DbSet ScormActivity { get; set; } + /// /// Gets or sets the RecentlyAddedResources. These are not entities. They are returned from the [resources].[GetRecentlyAddedResources] stored proc.. /// @@ -352,11 +369,63 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet NodePathNode { get; set; } + /// + /// Gets or sets the NodeViewModel. + /// + public virtual DbSet NodeViewModel { get; set; } + + + /// + /// Gets or sets the NodePathNodeViewModel. + /// + public virtual DbSet NodePathNodeViewModel { get; set; } + + /// + /// Gets or sets the NodeContentBrowseViewModel. + /// + public virtual DbSet NodeContentBrowseViewModel { get; set; } + + /// + /// Gets or sets the NodeContentEditorViewModel. + /// + public virtual DbSet NodeContentEditorViewModel { get; set; } + + + /// + /// Gets or sets the NodeContentAdminViewModel. + /// + public virtual DbSet NodeContentAdminViewModel { get; set; } + /// /// Gets or sets the node resource.. /// public virtual DbSet NodeResource { get; set; } + /// + /// Gets or sets the node resource lookup. + /// + public virtual DbSet NodeResourceLookup { get; set; } + + /// + /// Gets or sets the hierarchy edit. + /// + public virtual DbSet HierarchyEdit { get; set; } + + /// + /// Gets or sets the hierarchy edit detail. + /// + public virtual DbSet HierarchyEditDetail { get; set; } + + /// + /// Gets or sets the hierarchy edit detail. + /// + public virtual DbSet MoveResourceResultViewModel { get; set; } + + /// + /// Gets or sets the CacheOperationViewModel. + /// + public virtual DbSet CacheOperationViewModel { get; set; } + /// /// Gets or sets the Publication. /// @@ -387,6 +456,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet CatalogueAccessRequest { get; set; } + /// + /// Gets or sets the folder node version. + /// + public virtual DbSet FolderNodeVersion { get; set; } + /// /// Gets or sets the catalogue node version. /// @@ -437,6 +511,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet AssessmentResourceActivityInteractionAnswer { get; set; } + /// + /// Gets or sets the assessment resource activity match question. + /// + public virtual DbSet AssessmentResourceActivityMatchQuestion { get; set; } + /// /// Gets or sets the media resource activity.. /// @@ -602,6 +681,47 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet VideoAsset { get; set; } + + /// + /// Gets or sets UserBookmark. + /// + public virtual DbSet UserBookmark { get; set; } + + /// + /// Gets or sets the AllCatalogueAlphabet. + /// + public virtual DbSet AllCatalogueAlphabetModel { get; set; } + + /// + /// Gets or sets the AllCatalogueAlphabet. + /// + public virtual DbSet AllCatalogueViewModel { get; set; } + + /// + /// Gets or sets Provider. + /// + public virtual DbSet Provider { get; set; } + + /// + /// Gets or sets Resource Version Provider. + /// + public virtual DbSet ResourceVersionProvider { get; set; } + + /// + /// Gets or sets the resource activity. + /// + public virtual DbSet MyLearningActivity { get; set; } + + /// + /// Gets or sets the html resource version. + /// + public virtual DbSet HtmlResourceVersion { get; set; } + + /// + /// Gets or sets the AssessmentResourceActivityQuestionViewModel. + /// + public virtual DbSet AssessmentActivityCompletionViewModel { get; set; } + /// /// The on model creating. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/DateTimeHelper.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/DateTimeHelper.cs new file mode 100644 index 000000000..7133033da --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/DateTimeHelper.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Helpers +{ + using System; + + /// + /// Extension methods for DateTime class. + /// + public static class DateTimeHelper + { + /// + /// Returns the date of the first day of the week, based on the day of week passed in. + /// + /// The datetime. + /// The first day of the week. + /// The date of the first day of the week. + public static DateTime FirstDateInWeek(this DateTime dt, DayOfWeek weekStartDay) + { + while (dt.DayOfWeek != weekStartDay) + { + dt = dt.AddDays(-1); + } + + return dt; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/TextHelper.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/TextHelper.cs new file mode 100644 index 000000000..e14f17754 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Helpers/TextHelper.cs @@ -0,0 +1,37 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Helpers +{ + /// + /// The text helper. + /// + public static class TextHelper + { + /// + /// The Combine Date Components method. + /// + /// The authoed year. + /// The authored month. + /// The authored day of month. + /// The formatted date. + public static string CombineDateComponents(int? authoredYear, int? authoredMonth, int? authoredDayOfMonth) + { + string returnText = string.Empty; + if (authoredYear > 0) + { + returnText = authoredYear.ToString(); + + if (authoredMonth > 0) + { + string[] monthName = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + returnText = monthName[(int)authoredMonth - 1] + " " + returnText; + + if (authoredDayOfMonth > 0) + { + returnText = authoredDayOfMonth.ToString() + " " + returnText; + } + } + } + + return returnText; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionAnswerRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionAnswerRepository.cs new file mode 100644 index 000000000..2f57df508 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionAnswerRepository.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.EntityFrameworkCore; + + /// + /// The media resource activity interaction repository. + /// + public class AssessmentResourceActivityInteractionAnswerRepository : GenericRepository, IAssessmentResourceActivityInteractionAnswerRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public AssessmentResourceActivityInteractionAnswerRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.AssessmentResourceActivityInteractionAnswer.Where(n => n.Id == id).SingleOrDefaultAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionRepository.cs new file mode 100644 index 000000000..a5620f30d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityInteractionRepository.cs @@ -0,0 +1,87 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.EntityFrameworkCore; + + /// + /// The media resource activity interaction repository. + /// + public class AssessmentResourceActivityInteractionRepository : GenericRepository, IAssessmentResourceActivityInteractionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public AssessmentResourceActivityInteractionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.AssessmentResourceActivityInteraction.Where(n => n.Id == id).SingleOrDefaultAsync(); + } + + /// + /// Creates an assessment activity interaction. + /// + /// The user id. + /// The interaction. + /// The task. + public async Task CreateInteraction(int userId, AssessmentResourceActivityInteraction interaction) + { + foreach (AssessmentResourceActivityInteractionAnswer answer in interaction.Answers) + { + SetAuditFieldsForCreate(userId, answer); + } + + return await CreateAsync(userId, interaction); + } + + /// + /// Get the assessment resource activity interaction for the given user, activity, and question block. + /// + /// The user id. + /// The assessment resource activity id. + /// The question block id. + /// AssessmentResourceActivityInteraction. + public async Task GetInteractionForQuestion(int userId, int assessmentResourceActivityId, int questionBlockId) + { + return await DbContext + .AssessmentResourceActivityInteraction + .FirstOrDefaultAsync(i => i.CreateUserId == userId && i.AssessmentResourceActivityId == assessmentResourceActivityId && i.QuestionBlockId == questionBlockId); + } + + /// + /// Gets all the interactions for a given assessment resource activity. + /// + /// The assessment resource activity id. + /// The list of interactions. + public async Task> GetInteractionsForAssessmentResourceActivity(int assessmentResourceActivityId) + { + return await DbContext + .AssessmentResourceActivityInteraction + .Include(i => i.Answers) + .ThenInclude(a => a.QuestionAnswer) + .Include(i => i.Answers) + .Include(a => a.QuestionBlock) + .ThenInclude(q => q.Block) + .Include(a => a.QuestionBlock) + .ThenInclude(q => q.Answers) + .Where(i => i.AssessmentResourceActivityId == assessmentResourceActivityId) + .ToListAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityMatchQuestionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityMatchQuestionRepository.cs new file mode 100644 index 000000000..9a74aa5d4 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityMatchQuestionRepository.cs @@ -0,0 +1,37 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Collections.Generic; + using System.Linq; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + + /// + /// The AssessmentResourceActivityMatchQuestionRepository. + /// + public class AssessmentResourceActivityMatchQuestionRepository : GenericRepository, IAssessmentResourceActivityMatchQuestionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// dbContext. + /// The Timezone offset manager. + public AssessmentResourceActivityMatchQuestionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// Get Assessment Resource Activity by Assessment Resource Activity Id. + /// + /// The assessment resource activity id. + /// The Assessment Resource Activity. + public IEnumerable GetByAssessmentResourceActivityIdAsync(int id) + { + return DbContext + .AssessmentResourceActivityMatchQuestion + .Where(q => q.AssessmentResourceActivityId == id); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityRepository.cs new file mode 100644 index 000000000..d1ff349f3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/AssessmentResourceActivityRepository.cs @@ -0,0 +1,56 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.EntityFrameworkCore; + + /// + /// The activity repository. + /// + public class AssessmentResourceActivityRepository : GenericRepository, IAssessmentResourceActivityRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public AssessmentResourceActivityRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.AssessmentResourceActivity + .Include(a => a.ResourceActivity) + .Include(a => a.AssessmentResourceActivityInteractions) + .Include(a => a.MatchQuestions) + .ThenInclude(a => a.FirstMatchAnswer) + .Where(n => n.Id == id) + .SingleOrDefaultAsync(); + } + + /// + /// Gets the latest assessment resource activity for the given resource version id and user id. + /// + /// The resource version id. + /// The user id. + /// The assessment resource activity task. + public async Task GetLatestAssessmentResourceActivity(int resourceVersionId, int userId) + { + return await DbContext.AssessmentResourceActivity + .OrderByDescending(a => a.CreateDate) + .Include(a => a.ResourceActivity) + .FirstOrDefaultAsync(n => n.ResourceActivity.ResourceVersionId == resourceVersionId && n.CreateUserId == userId); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityInteractionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityInteractionRepository.cs new file mode 100644 index 000000000..476f2d37e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityInteractionRepository.cs @@ -0,0 +1,65 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The media resource activity interaction repository. + /// + public class MediaResourceActivityInteractionRepository : GenericRepository, IMediaResourceActivityInteractionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public MediaResourceActivityInteractionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.MediaResourceActivityInteraction.Where(n => n.Id == id).SingleOrDefaultAsync(); + } + + /// + /// Performs the analysis of media resource activity to populate the played segment data. + /// + /// The user id. + /// The resource version id. + /// The mediaResourceActivityId. + /// The . + public async Task CalculatePlayedMediaSegments(int userId, int resourceVersionId, int mediaResourceActivityId) + { + var resourceVersion = DbContext.ResourceVersion.FirstOrDefault(x => x.Id == resourceVersionId); + + if (resourceVersion == null) + { + throw new ArgumentException($"ResourceVersionId {resourceVersionId} not found."); + } + + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = resourceVersion.ResourceId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceVersion.MajorVersion }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = mediaResourceActivityId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = userId }; // This is not a mistake. SP has two different user Id params and in this scenario they are both the same value. + var param5 = new SqlParameter("@p5", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("activity.CalculatePlayedMediaSegments @p0, @p1, @p2, @p3, @p4, @p5", param0, param1, param2, param3, param4, param5); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityRepository.cs new file mode 100644 index 000000000..76b009135 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/MediaResourceActivityRepository.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.EntityFrameworkCore; + + /// + /// The activity repository. + /// + public class MediaResourceActivityRepository : GenericRepository, IMediaResourceActivityRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public MediaResourceActivityRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.MediaResourceActivity.Where(n => n.Id == id).SingleOrDefaultAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/NodeActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/NodeActivityRepository.cs new file mode 100644 index 000000000..23f242a2b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/NodeActivityRepository.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) NHS England. +// +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + + /// + /// The node activity repository. + /// + public class NodeActivityRepository : GenericRepository, INodeActivityRepository + { + /// + /// Initializes a new instance of the class. + /// + /// dbContext. + /// The Timezone offset manager. + public NodeActivityRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs new file mode 100644 index 000000000..12710bfe8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ResourceActivityRepository.cs @@ -0,0 +1,687 @@ +// +// Copyright (c) NHS England. +// +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Helpers; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The activity repository. + /// + public class ResourceActivityRepository : GenericRepository, IResourceActivityRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ResourceActivityRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.ResourceActivity + .Include(n => n.ResourceVersion) + .Include(n => n.NodePath) + .Include(n => n.LaunchResourceActivity) + .Where(n => n.Id == id).SingleOrDefaultAsync(); + } + + /// + /// Create activity record against a ResourceVersion. + /// + /// The user id. + /// The resource version id. + /// The node path id. + /// The launch resource activity id. + /// The activity Status Enum. + /// The activity Start. + /// The activity End. + /// Activity Id. + public int CreateActivity( + int userId, + int resourceVersionId, + int nodePathId, + int? launchResourceActivityId, + ActivityStatusEnum activityStatusEnum, + DateTimeOffset? activityStart, + DateTimeOffset? activityEnd) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceVersionId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = nodePathId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = (int)activityStatusEnum }; + var param4 = new SqlParameter("@p4", SqlDbType.DateTimeOffset) { Value = activityStart ?? (object)DBNull.Value }; + var param5 = new SqlParameter("@p5", SqlDbType.DateTimeOffset) { Value = activityEnd ?? (object)DBNull.Value }; + var param6 = new SqlParameter("@p6", SqlDbType.Decimal) { Value = DBNull.Value }; + var param7 = new SqlParameter("@p7", SqlDbType.Int) { Value = launchResourceActivityId ?? (object)DBNull.Value }; + var param8 = new SqlParameter("@p8", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param9 = new SqlParameter("@p9", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + DbContext.Database.ExecuteSqlRaw("activity.ResourceActivityCreate @p0, @p1, @p2, @p3, @p4, @p5,@p6, @p7, @p8, @p9 output", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9); + + return (int)param9.Value; + } + + /// + /// Get Resource Activity by user id. + /// + /// The user id. + /// ResourceActivity. + public IQueryable GetByUserId(int userId) + { + // For video/audio activities, only include them if they are finished - i.e. there is a second ResourceActivity record with status = 3 - completed. + // + // For assessment activities, only include the original activities that were created when starting the assessment. The created end record is only for consistency. + // It's easier to get the real assessment resource activity from the original resource activity, so only fetch that one. + // TD-4047: As part of this defect bringing back the removed code which then used for the new stored procedure created as part of performance improvement. + return DbContext.ResourceActivity + .Include(r => r.Resource) + .ThenInclude(r => r.ResourceReference) + .Include(r => r.MediaResourceActivity) + .Include(r => r.ScormActivity) + .Include(r => r.ResourceVersion.VideoResourceVersion) + .Include(r => r.ResourceVersion.AudioResourceVersion) + .Include(r => r.ResourceVersion.ScormResourceVersion) + .Include(r => r.ResourceVersion.ScormResourceVersion.ScormResourceVersionManifest) + .Include(r => r.ResourceVersion.AssessmentResourceVersion.AssessmentContent.Blocks) + .Include(r => r.AssessmentResourceActivity) + .ThenInclude(a => a.AssessmentResourceActivityInteractions) + .Include(r => r.NodePath) + .AsNoTracking() + .Where(r => + r.UserId == userId && r.ScormActivity.First().CmiCoreLessonStatus != (int)ActivityStatusEnum.Completed && + (!r.InverseLaunchResourceActivity.Any() || + r.InverseLaunchResourceActivity.Any())) + .OrderByDescending(r => r.ActivityStart); + } + + /// + /// Gets a list of incomplete media activities. Those that for any reason, the end of the user's activity was not recorded normally. For example - browser crash, power loss, connection loss. + /// + /// The user id. + /// The . + public async Task> GetIncompleteMediaActivities(int userId) + { + var incompleteActivities = await DbContext.ResourceActivity + .Include(x => x.MediaResourceActivity) + .ThenInclude(x => x.MediaResourceActivityInteraction) + .Where(x => + x.MediaResourceActivity != null && + x.UserId == userId && + !x.LaunchResourceActivityId.HasValue && + !x.InverseLaunchResourceActivity.Any(y => y.ActivityStatusId == (int)ActivityStatusEnum.Completed)) + .OrderBy(x => x.ActivityStart) + .ToListAsync(); + + return incompleteActivities; + } + + /// + /// Gets a list of all the user's activities for a given resource version. + /// + /// The user id.> + /// The resource version id. + /// The . + public async Task> GetAllTheActivitiesFor(int userId, int resourceVersionId) + { + var activities = await GetByUserId(userId) + .Where(r => r.ResourceVersionId == resourceVersionId) + .OrderBy(r => r.AmendDate) + .ToListAsync(); + + return activities; + } + + /// + /// Check if scorm activity has been completed. + /// + /// The user id. + /// The scormActivityId id. + /// The . + public async Task IsScormActivityFinished(int userId, int scormActivityId) + { + var finishedActivity = await DbContext.ResourceActivity + .Include(r => r.ScormActivity) + .FirstOrDefaultAsync(x => + x.UserId == userId && + x.ScormActivity.Any(x => x.Id == scormActivityId) && + x.LaunchResourceActivityId.HasValue && + x.InverseLaunchResourceActivity.Any(y => y.ActivityStatusId == (int)ActivityStatusEnum.Completed || + y.ActivityStatusId == (int)ActivityStatusEnum.Passed || y.ActivityStatusId == (int)ActivityStatusEnum.Failed)); + return finishedActivity != null; + } + + /// + /// Get Resource Activity by user id. + /// + /// The user id. + /// requestModel. + /// detailedMediaActivityRecordingStartDate. + /// ResourceActivity. + public async Task> GetByUserIdFromSP(int userId,Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate) + { + (DateTimeOffset? startDate, DateTimeOffset? endDate) = this.ApplyDatesFilter(requestModel); + (string strResourceTypes, bool resourceTypeFlag) = this.ApplyResourceTypesfilters(requestModel); + (string strActivityStatus, bool activityStatusEnumFlag) = this.ApplyActivityStatusFilter(requestModel); + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@searchText", SqlDbType.NVarChar) { Value = requestModel.SearchText == null ? DBNull.Value : requestModel.SearchText }; + var param2 = new SqlParameter("@activityStatuses", SqlDbType.NVarChar) { Value = activityStatusEnumFlag == false ? DBNull.Value : strActivityStatus }; + var param3 = new SqlParameter("@resourceTypes", SqlDbType.NVarChar) { Value = resourceTypeFlag == false ? DBNull.Value : strResourceTypes }; + var param4 = new SqlParameter("@activityStartDate", SqlDbType.DateTimeOffset) { Value = startDate == null ? DBNull.Value : startDate }; + var param5 = new SqlParameter("@activityEndDate", SqlDbType.DateTimeOffset) { Value = endDate == null ? DBNull.Value : endDate }; + var param6 = new SqlParameter("@mediaActivityRecordingStartDate", SqlDbType.DateTimeOffset) { Value = detailedMediaActivityRecordingStartDate }; + var param7 = new SqlParameter("@certificateEnabled", SqlDbType.Bit) { Value = requestModel.CertificateEnabled == false ? DBNull.Value : requestModel.CertificateEnabled }; + var param8 = new SqlParameter("@offSet", SqlDbType.Int) { Value = requestModel.Skip }; + var param9 = new SqlParameter("@fetchRows", SqlDbType.Int) { Value = requestModel.Take }; + var result = await DbContext.MyLearningActivity.FromSqlRaw("[activity].[GetUserLearningActivities] @userId, @searchText, @activityStatuses, @resourceTypes,@activityStartDate,@activityEndDate,@mediaActivityRecordingStartDate,@certificateEnabled,@offSet,@fetchRows ", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9) + .AsNoTracking().ToListAsync(); + + // Bind result to ResourceActivity model + this.BindNestedData(result); + List listOfresourceActivities = result.Select(i => new ResourceActivity() + { + ActivityEnd = i.ActivityEnd, + ActivityStart = i.ActivityStart, + ActivityStatusId = i.ActivityStatusId, + AmendDate = i.AmendDate, + AmendUserId = i.AmendUserId, + AssessmentResourceActivity = i.AssessmentResourceActivity, + CreateDate = i.CreateDate, + CreateUserId = i.CreateUserId, + Deleted = i.Deleted, + DurationSeconds = i.DurationSeconds ?? 0, + Id = i.Id, + InverseLaunchResourceActivity = i.Resource_InverseLaunchResourceActivity, + LaunchResourceActivityId = i.LaunchResourceActivityId, + MajorVersion = i.MajorVersion, + MediaResourceActivity = i.MediaResourceActivity, + MinorVersion = i.MinorVersion, + NodePath = i.NodePath, + NodePathId = i.NodePathId, + Resource = i.Resource, + ResourceId = i.ResourceId, + ResourceVersion = i.ResourceVersion, + ResourceVersionId = i.ResourceVersionId, + Score = i.Score, + ScormActivity = i.ScormActivity, + UserId = i.UserId, + }).ToList(); + + return listOfresourceActivities.OrderByDescending(r => r.ActivityStart).AsQueryable(); + } + + /// + /// Get Resource Activity by user id. + /// + /// The user id. + /// requestModel. + /// detailedMediaActivityRecordingStartDate. + /// ResourceActivity. + public int GetTotalCount(int userId, Nhs.Models.MyLearning.MyLearningRequestModel requestModel, DateTimeOffset detailedMediaActivityRecordingStartDate) + { + (DateTimeOffset? startDate, DateTimeOffset? endDate) = this.ApplyDatesFilter(requestModel); + (string strResourceTypes, bool resourceTypeFlag) = this.ApplyResourceTypesfilters(requestModel); + (string strActivityStatus, bool activityStatusEnumFlag) = this.ApplyActivityStatusFilter(requestModel); + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@searchText", SqlDbType.NVarChar) { Value = requestModel.SearchText == null ? DBNull.Value : requestModel.SearchText }; + var param2 = new SqlParameter("@activityStatuses", SqlDbType.NVarChar) { Value = activityStatusEnumFlag == false ? DBNull.Value : strActivityStatus }; + var param3 = new SqlParameter("@resourceTypes", SqlDbType.NVarChar) { Value = resourceTypeFlag == false ? DBNull.Value : strResourceTypes }; + var param4 = new SqlParameter("@activityStartDate", SqlDbType.DateTimeOffset) { Value = startDate == null ? DBNull.Value : startDate }; + var param5 = new SqlParameter("@activityEndDate", SqlDbType.DateTimeOffset) { Value = endDate == null ? DBNull.Value : endDate }; + var param6 = new SqlParameter("@mediaActivityRecordingStartDate", SqlDbType.DateTimeOffset) { Value = detailedMediaActivityRecordingStartDate }; + var param7 = new SqlParameter("@certificateEnabled", SqlDbType.Bit) { Value = requestModel.CertificateEnabled == false ? DBNull.Value : requestModel.CertificateEnabled }; + var param8 = new SqlParameter("@totalcount", SqlDbType.Int) { Direction = ParameterDirection.Output }; + DbContext.Database.ExecuteSqlRaw("[activity].[GetUserLearningActivitiesCount] @userId, @searchText, @activityStatuses, @resourceTypes,@activityStartDate,@activityEndDate,@mediaActivityRecordingStartDate,@certificateEnabled,@totalcount output", param0, param1, param2, param3, param4, param5, param6, param7, param8); + + return (int)param8.Value; + } + + /// + /// Gets a list of all the user's activities for a given resource version. + /// + /// The user id.> + /// The resource version id. + /// The . + public async Task> GetAllTheActivitiesFromSP(int userId, int resourceVersionId) + { + var activities = GetByUserIdWithResourceVersionId(userId, resourceVersionId).Result.OrderBy(r => r.AmendDate).ToList(); + return activities; + } + + /// + /// Gets a list of all the user's activities for a given resource version. + /// + /// The user id.> + /// The resource version id. + /// The resource activity id. + /// The . + public async Task GetAssessmentActivityCompletionPercentage(int userId, int resourceVersionId, int activityId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceVersionId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = activityId }; + + var retVal = await DbContext.AssessmentActivityCompletionViewModel + .FromSqlRaw("EXEC activity.GetAssessmentActivityCompletionPercentage @p0, @p1, @p2", param0, param1, param2) + .AsNoTracking() + .ToListAsync(); + + AssessmentActivityCompletionViewModel assessmentResourceActivityQuestion = retVal.FirstOrDefault(); + return assessmentResourceActivityQuestion; + } + + /// + /// Get Resource Activity by user id. + /// + /// The user id. + /// The resource Version Id. + /// ResourceActivity. + public async Task> GetByUserIdWithResourceVersionId(int userId, int resourceVersionId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@resourceVersionId", SqlDbType.Int) { Value = resourceVersionId }; + var result = await DbContext.MyLearningActivity.FromSqlRaw("[activity].[GetUserLatestActivityCheck] @userId, @resourceVersionId ", param0, param1) + .AsNoTracking().ToListAsync(); + + // Bind result to ResourceActivity model + this.BindNestedData(result); + List listOfresourceActivities = result.Select(i => new ResourceActivity() + { + ActivityEnd = i.ActivityEnd, + ActivityStart = i.ActivityStart, + ActivityStatusId = i.ActivityStatusId, + AmendDate = i.AmendDate, + AmendUserId = i.AmendUserId, + AssessmentResourceActivity = i.AssessmentResourceActivity, + CreateDate = i.CreateDate, + CreateUserId = i.CreateUserId, + Deleted = i.Deleted, + DurationSeconds = i.DurationSeconds ?? 0, + Id = i.Id, + InverseLaunchResourceActivity = i.Resource_InverseLaunchResourceActivity, + LaunchResourceActivityId = i.LaunchResourceActivityId, + MajorVersion = i.MajorVersion, + MediaResourceActivity = i.Resource_MediaResourceActivity, + MinorVersion = i.MinorVersion, + NodePath = i.NodePath, + NodePathId = i.NodePathId, + Resource = i.Resource, + ResourceId = i.ResourceId, + ResourceVersion = i.ResourceVersion, + ResourceVersionId = i.ResourceVersionId, + Score = i.Score, + ScormActivity = i.ScormActivity, + UserId = i.UserId, + }).ToList(); + return listOfresourceActivities.OrderByDescending(r => r.ActivityStart).AsQueryable(); + } + + private void BindNestedData(List result) + { + BindNodePathNestedData(result); + BindResourceNestedData(result); + BindResourceVersionData(result); + BindScormActivityNestedData(result); + BindAssessmentResourceActivityNestedData(result); + BindMediaResourceActivityNestedData(result); + BindResourceVersionBlockData(result); + BindScormResourceVersionManifestNestedData(result); + } + + private void BindScormResourceVersionManifestNestedData(List result) + { + result.ToList().ForEach(i => + { + i.ResourceVersion.ScormResourceVersion = new ScormResourceVersion(); + i.ResourceVersion.ScormResourceVersion.ScormResourceVersionManifest = new ScormResourceVersionManifest(); + i.ResourceVersion.ScormResourceVersion.ScormResourceVersionManifest.MasteryScore = i.ScormResourceVersionManifest_MasteryScore; + }); + } + + private void BindResourceVersionBlockData(List result) + { + result.ToList().ForEach(i => + { + i.ResourceVersion.AssessmentResourceVersion = new AssessmentResourceVersion(); + int assessmentType = i.ResourceVersion_AssessmentResourceVersion_AssessmentType ?? 0; + i.ResourceVersion.AssessmentResourceVersion.AssessmentType = (AssessmentTypeEnum)assessmentType; + i.ResourceVersion.AssessmentResourceVersion.PassMark = i.ResourceVersion_PassMark; + }); + } + + private void BindMediaResourceActivityNestedData(List result) + { + result.ToList().ForEach(i => + { + if (i.MediaResourceActivity_ResourceActivityId != null) + { + MediaResourceActivity mediaResourceActivity = new MediaResourceActivity(); + + mediaResourceActivity.ResourceActivityId = i.MediaResourceActivity_ResourceActivityId ?? 0; + mediaResourceActivity.Id = i.MediaResourceActivity_ResourceActivityId ?? 0; + mediaResourceActivity.ActivityStart = i.MediaResourceActivity_ActivityStart ?? DateTimeOffset.MinValue; + mediaResourceActivity.SecondsPlayed = i.MediaResourceActivity_SecondsPlayed; + mediaResourceActivity.PercentComplete = i.MediaResourceActivity_PercentComplete; + i.MediaResourceActivity.Add(mediaResourceActivity); + } + }); + } + + private void BindAssessmentResourceActivityNestedData(List result) + { + result.ToList().ForEach(i => + { + if (i.AssessmentResourceActivity_ResourceActivityId != null) + { + AssessmentResourceActivity assessmentResourceActivity = new AssessmentResourceActivity(); + assessmentResourceActivity.ResourceActivityId = i.AssessmentResourceActivity_ResourceActivityId ?? 0; + assessmentResourceActivity.Id = i.AssessmentResourceActivity_Id ?? 0; + assessmentResourceActivity.Score = i.AssessmentResourceActivity_Score; + assessmentResourceActivity.Reason = i.AssessmentResourceActivity_Reason; + i.AssessmentResourceActivity.Add(assessmentResourceActivity); + } + }); + } + + private void BindScormActivityNestedData(List result) + { + result.ToList().ForEach(i => + { + ScormActivity scormActivity = new ScormActivity(); + scormActivity.CmiCoreExit = i.ScormActivity_CmiCoreExit; + scormActivity.CmiCoreLessonLocation = i.ScormActivity_CmiCoreLessonLocation; + scormActivity.CmiCoreLessonStatus = i.ScormActivity_CmiCoreLessonStatus; + scormActivity.CmiCoreScoreMin = i.ScormActivity_CmiCoreScoreMin; + scormActivity.CmiCoreScoreMax = i.ScormActivity_CmiCoreScoreMax; + scormActivity.CmiCoreScoreRaw = i.ScormActivity_CmiCoreScoreRaw; + scormActivity.CmiCoreSessionTime = i.ScormActivity_CmiCoreSessionTime; + scormActivity.CmiSuspendData = i.ScormActivity_CmiSuspendData; + scormActivity.DurationSeconds = i.ScormActivity_DurationSeconds ?? 0; + i.ScormActivity.Add(scormActivity); + }); + } + + private void BindResourceVersionData(List result) + { + result.ToList().ForEach(i => + { + i.ResourceVersion.AdditionalInformation = i.ResourceVersion_AdditionalInformation; + i.ResourceVersion.AmendDate = i.ResourceVersion_AmendDate; + i.ResourceVersion.AmendUserId = i.ResourceVersion_AmendUserId; + i.ResourceVersion.ArticleResourceVersion = i.ResourceVersion_ArticleResourceVersion; + AudioResourceVersion audioResourceVersion = new AudioResourceVersion(); + audioResourceVersion.DurationInMilliseconds = i.AudioResourceVersion_DurationInMilliseconds; + i.ResourceVersion.AudioResourceVersion = audioResourceVersion; + i.ResourceVersion.CaseResourceVersion = i.ResourceVersion_CaseResourceVersion; + i.ResourceVersion.CertificateEnabled = i.ResourceVersion_CertificateEnabled; + i.ResourceVersion.Cost = i.ResourceVersion_Cost; + i.ResourceVersion.Deleted = i.ResourceVersion_Deleted; + i.ResourceVersion.Description = i.ResourceVersion_Description; + i.ResourceVersion.EmbeddedResourceVersion = i.ResourceVersion_EmbeddedResourceVersion; + i.ResourceVersion.EquipmentResourceVersion = i.ResourceVersion_EquipmentResourceVersion; + i.ResourceVersion.FileChunkDetail = i.ResourceVersion_FileChunkDetail; + i.ResourceVersion.GenericFileResourceVersion = i.ResourceVersion_GenericFileResourceVersion; + i.ResourceVersion.HasCost = i.ResourceVersion_HasCost; + i.ResourceVersion.HtmlResourceVersion = i.ResourceVersion_HtmlResourceVersion; + i.ResourceVersion.ImageResourceVersion = i.ResourceVersion_ImageResourceVersion; + i.ResourceVersion.MajorVersion = i.ResourceVersion_MajorVersion; + i.ResourceVersion.MinorVersion = i.ResourceVersion_MinorVersion; + i.ResourceVersion.Publication = i.ResourceVersion_Publication; + i.ResourceVersion.PublicationId = i.ResourceVersion_PublicationId; + i.ResourceVersion.Resource = i.ResourceVersion_Resource; + i.ResourceVersion.ResourceAccessibilityEnum = i.ResourceVersion_ResourceAccessibilityEnum; + i.ResourceVersion.ResourceId = i.ResourceVersion_ResourceId; + i.ResourceVersion.ResourceLicence = i.ResourceVersion_ResourceLicence; + i.ResourceVersion.ResourceLicenceId = i.ResourceVersion_ResourceLicenceId; + i.ResourceVersion.ResourceVersionAuthor = i.ResourceVersion_ResourceVersionAuthor; + i.ResourceVersion.ResourceVersionEvent = i.ResourceVersion_ResourceVersionEvent; + i.ResourceVersion.ResourceVersionFlag = i.ResourceVersion_ResourceVersionFlag; + i.ResourceVersion.ResourceVersionKeyword = i.ResourceVersion_ResourceVersionKeyword; + i.ResourceVersion.ResourceVersionProvider = i.ResourceVersion_ResourceVersionProvider; + i.ResourceVersion.ResourceVersionRatingSummary = i.ResourceVersion_ResourceVersionRatingSummary; + i.ResourceVersion.ResourceVersionRatings = i.ResourceVersion_ResourceVersionRatings; + int versionStatusid = i.ResourceVersion_VersionStatusId ?? 0; + i.ResourceVersion.VersionStatusEnum = (VersionStatusEnum)versionStatusid; + i.ResourceVersion.ResourceVersionValidationResult = i.ResourceVersion_ResourceVersionValidationResult; + i.ResourceVersion.ResourceWhereCurrent = i.ResourceVersion_ResourceWhereCurrent; + i.ResourceVersion.ReviewDate = i.ResourceVersion_ReviewDate; + i.ResourceVersion.ScormResourceVersion = i.ResourceVersion_ScormResourceVersion; + i.ResourceVersion.SensitiveContent = i.ResourceVersion_SensitiveContent; + i.ResourceVersion.Title = i.ResourceVersion_Title; + i.ResourceVersion.VersionStatusEnum = i.ResourceVersion_VersionStatusEnum; + VideoResourceVersion vedioResourceVersion = new VideoResourceVersion(); + vedioResourceVersion.DurationInMilliseconds = i.VideoResourcVersion_DurationInMilliseconds; + i.ResourceVersion.VideoResourceVersion = vedioResourceVersion; + i.ResourceVersion.WebLinkResourceVersion = i.ResourceVersion_WebLinkResourceVersion; + }); + } + + private void BindResourceNestedData(List result) + { + result.ToList().ForEach(i => + { + i.Resource.AmendDate = i.Resource_AmendDate; + i.Resource.AmendUserId = i.Resource_AmendUserId; + i.Resource.CreateUserId = i.Resource_CreateUserId; + i.Resource.CreateDate = i.Resource_CreateDate; + i.Resource.CurrentResourceVersionId = i.Resource_CurrentResourceVersionId; + i.Resource.Deleted = i.Resource_Deleted; + int resourceTypeId = i.Resource_ResourceTypeId; + i.Resource.ResourceTypeEnum = (ResourceTypeEnum)resourceTypeId; + var resourceReferences = result.Where(x => x.Id == i.Id && x.ResourceVersionId == i.ResourceVersionId).ToList(); + List resourceReferenceList = new List(); + foreach (var b in resourceReferences) + { + ResourceReference resourceReference = new ResourceReference(); + resourceReference.OriginalResourceReferenceId = b.ResourceReference_OriginalResourceReferenceId; + resourceReference.NodePathId = b.ResourceReference_NodePathId; + resourceReference.ResourceId = b.ResourceReference_ResourceId; + resourceReferenceList.Add(resourceReference); + } + + i.Resource.ResourceReference = resourceReferenceList; + }); + } + + private void BindNodePathNestedData(List result) + { + result.ToList().ForEach(i => + { + i.NodePath.AmendDate = i.NodePath_AmendDate; + i.NodePath.AmendUserId = i.NodePath_AmendUserId; + i.NodePath.CatalogueNodeId = i.NodePath_CatalogueNode; + i.NodePath.CreateDate = i.NodePath_CreateDate; + i.NodePath.CreateUserId = i.NodePath_CreateUserId; + i.NodePath.Deleted = i.NodePath_Deleted; + i.NodePath.Id = i.NodePath_NodeId; + i.NodePath.IsActive = i.NodePath_IsActive; + i.NodePath.NodeId = i.NodePath_NodeId; + i.NodePath.NodePathString = i.NodePath_NodePathString; + }); + } + + private (DateTimeOffset? startDate, DateTimeOffset? endDate) ApplyDatesFilter(MyLearningRequestModel requestModel) + { + DateTimeOffset? startDate = null; + DateTimeOffset? endDate = null; + if (!string.IsNullOrEmpty(requestModel.TimePeriod)) + { + var now = DateTime.Now.Date; + + if (requestModel.TimePeriod == "dateRange") + { + if (requestModel.StartDate.HasValue || requestModel.EndDate.HasValue) + { + if (requestModel.StartDate.HasValue) + { + startDate = requestModel.StartDate; + } + + if (requestModel.EndDate.HasValue) + { + endDate = requestModel.EndDate.Value.AddDays(1); + } + } + else + { + throw new ArgumentException("If RequestModel.TimePeriod is set to 'dateRange', the RequestModel.StartDate and/or EndDate must also be specified."); + } + } + else if (requestModel.TimePeriod == "thisWeek") + { + // Definition of this week is anything from the Monday prior, or today if today is Monday. + var firstDateOfWeek = now.FirstDateInWeek(DayOfWeek.Monday); + startDate = firstDateOfWeek; + } + else if (requestModel.TimePeriod == "thisMonth") + { + // Definition of this month is anything from the 1st of the current month. + startDate = new DateTime(now.Year, now.Month, 1); + } + else if (requestModel.TimePeriod == "last12Months") + { + // Definition of the last 12 months is anything from the same date 12 months ago. e.g. if today is 7th Oct 2020, then return anything from 7th Oct 2019. + startDate = now.AddMonths(-12); + } + } + + return (startDate, endDate); + } + + private (string strResourceTypes, bool resourceTypeFlag) ApplyResourceTypesfilters(MyLearningRequestModel requestModel) + { + var listOfResourceTypeEnum = Enum.GetValues(typeof(ResourceTypeEnum)).Cast().ToList(); + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Undefined); + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Embedded); + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Equipment); + bool resourceTypeFlag = false; + + // Resource Type filter. + if (requestModel.Article || requestModel.Audio || requestModel.Elearning || requestModel.Html || requestModel.File || requestModel.Image || requestModel.Video || requestModel.Weblink || requestModel.Assessment || requestModel.Case) + { + if (!requestModel.Article) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Article); + } + + if (!requestModel.Audio) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Audio); + } + + if (!requestModel.Elearning) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Scorm); + } + + if (!requestModel.Html) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Html); + } + + if (!requestModel.File) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.GenericFile); + } + + if (!requestModel.Image) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Image); + } + + if (!requestModel.Video) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Video); + } + + if (!requestModel.Weblink) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.WebLink); + } + + if (!requestModel.Assessment) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Assessment); + } + + if (!requestModel.Case) + { + listOfResourceTypeEnum.Remove((int)ResourceTypeEnum.Case); + } + + resourceTypeFlag = true; + } + + return (string.Join(",", listOfResourceTypeEnum), resourceTypeFlag); + } + + private (string strActivityStatus, bool activityStatusEnumFlag) ApplyActivityStatusFilter(MyLearningRequestModel requestModel) + { + var listOfactivityStatusesEnum = Enum.GetValues(typeof(ActivityStatusEnum)).Cast().ToList(); + var activityStatusEnumFlag = false; + if (requestModel.Complete || requestModel.Incomplete || requestModel.Passed || requestModel.Failed || requestModel.Downloaded || requestModel.Launched || requestModel.Viewed) + { + activityStatusEnumFlag = true; + if (!requestModel.Complete) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Completed); + } + + if (!requestModel.Passed) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Passed); + } + + if (!requestModel.Failed) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Failed); + } + + if (!requestModel.Downloaded) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Downloaded); + } + + if (!requestModel.Incomplete) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Incomplete); + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.InProgress); + } + + if (!requestModel.Launched) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Launched); + } + + if (!requestModel.Viewed) + { + listOfactivityStatusesEnum.Remove((int)ActivityStatusEnum.Viewed); + } + } + + return (string.Join(",", listOfactivityStatusesEnum), activityStatusEnumFlag); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ScormActivityRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ScormActivityRepository.cs new file mode 100644 index 000000000..cfcef4ebb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Activity/ScormActivityRepository.cs @@ -0,0 +1,302 @@ +// +// Copyright (c) NHS England. +// +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Activity +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Dto; + using LearningHub.Nhs.Models.Entities.Activity; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Activity; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The activity repository. + /// + public class ScormActivityRepository : GenericRepository, IScormActivityRepository + { + private const int IndexKeyResetValue = 0; + + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ScormActivityRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.ScormActivity.AsNoTracking().Where(s => s.Id == id) + .Include(sao => sao.ScormActivityInteraction) + .ThenInclude(sai => sai.ScormActivityInteractionCorrectResponse).AsNoTracking() + .Include(sai => sai.ScormActivityInteraction) + .ThenInclude(sai => sai.ScormActivityInteractionObjective).AsNoTracking() + .Include(sao => sao.ScormActivityObjective).AsNoTracking().SingleOrDefaultAsync(); + } + + /// + /// Create scorm activity. + /// + /// The user id. + /// The resource reference id. + /// Scorm Activity Id. + public int Create(int userId, int resourceReferenceId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceReferenceId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + DbContext.Database.ExecuteSqlRaw("[activity].[ScormActivityCreate] @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); + + return (int)param3.Value; + } + + /// + /// Complete scorm activity. + /// Returns the resource activity id of the completion event. + /// + /// The user id. + /// The scorm activity id. + /// Resource Activity Id. + public int Complete(int userId, int scormActivityId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = scormActivityId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + DbContext.Database.ExecuteSqlRaw("[activity].[ScormActivityComplete] @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); + + return (int)param3.Value; + } + + /// + /// Resolve scorm activity. + /// + /// The scorm activity id. + public void Resolve(int scormActivityId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = scormActivityId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + DbContext.Database.ExecuteSqlRaw("[activity].[ScormActivityResolve] @p0, @p1", param0, param1); + } + + /// + /// Gets the previously launched incomplete scrom activity summary. + /// + /// userId. + /// resourceReferenceId. + /// ScormActivitySummaryDto. + public ScormActivitySummaryDto GetScormActivitySummary(int userId, int resourceReferenceId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceReferenceId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Direction = ParameterDirection.Output }; + var param3 = new SqlParameter("@p3", SqlDbType.NVarChar) { Direction = ParameterDirection.Output, Size = 20 }; + + DbContext.Database.ExecuteSqlRaw("[activity].[ScormActivityGetSummary] @p0, @p1, @p2 output, @p3 output", param0, param1, param2, param3); + var scormActivitySummaryDto = new ScormActivitySummaryDto + { + IncompleteActivityId = (int?)(param2.Value == DBNull.Value ? null : param2.Value), + TotalTime = (string)(param3.Value == DBNull.Value ? null : param3.Value), + }; + return scormActivitySummaryDto; + } + + /// + /// The update async. + /// + /// The user id. + /// The scorm activity entity. + /// The . + public override async Task UpdateAsync(int userId, ScormActivity updatedScormActivity) + { + try + { + await UpdateScormActivityAsync(userId, updatedScormActivity); + } + + // Re-try one more time if sql Unique constraint error + catch (DbUpdateException e) when (e.InnerException is SqlException sqlEx && sqlEx?.Number == 2627) + { + DbContext.ChangeTracker.Clear(); + await UpdateScormActivityAsync(userId, updatedScormActivity); + } + } + + /// + /// Clones the incomplete scrom activity session with the newly created session. + /// + /// incompleteScromActivityId. + /// scromActivityId. + /// Cloned ScormActivity. + public ScormActivity Clone(int incompleteScormActivityId, int scormActivityId) + { + var incompleteScormActivity = GetByIdAsync(incompleteScormActivityId).Result; + + var scormActivity = DbContext.ScormActivity + .Where(s => s.Id == scormActivityId).SingleOrDefault(); + + foreach (var interaction in incompleteScormActivity.ScormActivityInteraction) + { + interaction.Id = IndexKeyResetValue; + interaction.ScormActivityId = IndexKeyResetValue; + + foreach (var correctResponse in interaction.ScormActivityInteractionCorrectResponse) + { + correctResponse.Id = IndexKeyResetValue; + correctResponse.ScormActivityInteractionId = IndexKeyResetValue; + this.SetAuditFieldsForCreate(scormActivity.CreateUserId, correctResponse); + } + + foreach (var objective in interaction.ScormActivityInteractionObjective) + { + objective.Id = IndexKeyResetValue; + objective.ScormActivityInteractionId = IndexKeyResetValue; + this.SetAuditFieldsForCreate(scormActivity.CreateUserId, objective); + } + + this.SetAuditFieldsForCreate(scormActivity.CreateUserId, interaction); + scormActivity.ScormActivityInteraction.Add(interaction); + } + + foreach (var objective in incompleteScormActivity.ScormActivityObjective) + { + objective.ScormActivityId = IndexKeyResetValue; + objective.Id = IndexKeyResetValue; + this.SetAuditFieldsForCreate(scormActivity.CreateUserId, objective); + scormActivity.ScormActivityObjective.Add(objective); + } + + scormActivity.CmiCoreLessonStatus = incompleteScormActivity.CmiCoreLessonStatus; + scormActivity.CmiCoreLessonLocation = incompleteScormActivity.CmiCoreLessonLocation; + scormActivity.CmiCoreScoreRaw = incompleteScormActivity.CmiCoreScoreRaw; + scormActivity.CmiCoreScoreMin = incompleteScormActivity.CmiCoreScoreMin; + scormActivity.CmiCoreScoreMax = incompleteScormActivity.CmiCoreScoreMax; + scormActivity.CmiCoreExit = incompleteScormActivity.CmiCoreExit; + scormActivity.CmiSuspendData = incompleteScormActivity.CmiSuspendData; + + this.SetAuditFieldsForUpdate(scormActivity.CreateUserId, scormActivity); + DbContext.SaveChangesAsync(); + + return scormActivity; + } + + /// + /// Check user scorm activity data suspend data need to be cleared. + /// + /// last scorm activity id. + /// resource version id. + /// boolean. + public async Task CheckUserScormActivitySuspendDataToBeCleared(int lastScormActivityId, int resourceVersionId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = lastScormActivityId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = resourceVersionId }; + var param2 = new SqlParameter("@p2", SqlDbType.Bit) { Direction = ParameterDirection.Output }; + + string sql = "activity.UserScormActivitySuspendDataToBeCleared @p0, @p1, @p2 output"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + + return (bool)param2.Value; + } + + private async Task UpdateScormActivityAsync(int userId, ScormActivity updatedScormActivity) + { + var existingScormActivity = DbContext.ScormActivity.Where(s => s.Id == updatedScormActivity.Id) + .Include(sao => sao.ScormActivityObjective) + .Include(sao => sao.ScormActivityInteraction) + .ThenInclude(sai => sai.ScormActivityInteractionCorrectResponse) + .Include(sai => sai.ScormActivityInteraction) + .ThenInclude(sai => sai.ScormActivityInteractionObjective) + .SingleOrDefault(); + + updatedScormActivity.ResourceActivityId = existingScormActivity.ResourceActivityId; + + DbContext.Entry(existingScormActivity).CurrentValues.SetValues(updatedScormActivity); + this.SetAuditFieldsForUpdate(userId, existingScormActivity); + + foreach (var interaction in updatedScormActivity.ScormActivityInteraction) + { + var existingInteraction = existingScormActivity.ScormActivityInteraction + .Where(i => i.ScormActivityId == existingScormActivity.Id && i.SequenceNumber == interaction.SequenceNumber).OrderByDescending(i => i.CreateDate).FirstOrDefault(); + if (existingInteraction != null) + { + interaction.Id = existingInteraction.Id; + DbContext.Entry(existingInteraction).CurrentValues.SetValues(interaction); + this.SetAuditFieldsForUpdate(userId, existingInteraction); + foreach (var updatedCorrectResponse in interaction.ScormActivityInteractionCorrectResponse) + { + var existingInteractionCorrectResponse = existingInteraction.ScormActivityInteractionCorrectResponse.Where(cr => cr.Index == updatedCorrectResponse.Index).SingleOrDefault(); + if (existingInteractionCorrectResponse != null) + { + existingInteractionCorrectResponse.Pattern = updatedCorrectResponse.Pattern; + this.SetAuditFieldsForUpdate(userId, existingInteractionCorrectResponse); + } + } + + foreach (var updatedObjective in interaction.ScormActivityInteractionObjective) + { + var existingInteractionObjective = existingInteraction.ScormActivityInteractionObjective.Where(o => o.Index == updatedObjective.Index).SingleOrDefault(); + if (existingInteractionObjective != null) + { + existingInteractionObjective.ObjectiveId = updatedObjective.ObjectiveId; + this.SetAuditFieldsForUpdate(userId, existingInteractionObjective); + } + } + } + else + { + SetAuditFieldsForCreate(userId, interaction); + foreach (var correctResponse in interaction.ScormActivityInteractionCorrectResponse) + { + SetAuditFieldsForCreate(userId, correctResponse); + } + + foreach (var objective in interaction.ScormActivityInteractionObjective) + { + SetAuditFieldsForCreate(userId, objective); + } + + existingScormActivity.ScormActivityInteraction.Add(interaction); + } + } + + foreach (var objective in updatedScormActivity.ScormActivityObjective) + { + var existingObjective = existingScormActivity.ScormActivityObjective + .Where(i => i.ScormActivityId == existingScormActivity.Id && i.SequenceNumber == objective.SequenceNumber).OrderByDescending(o => o.CreateDate).FirstOrDefault(); + if (existingObjective != null) + { + objective.Id = existingObjective.Id; + DbContext.Entry(existingObjective).CurrentValues.SetValues(objective); + this.SetAuditFieldsForUpdate(userId, existingObjective); + } + else + { + SetAuditFieldsForCreate(userId, objective); + existingScormActivity.ScormActivityObjective.Add(objective); + } + } + + await DbContext.SaveChangesAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/BookmarkRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/BookmarkRepository.cs new file mode 100644 index 000000000..2c13ad940 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/BookmarkRepository.cs @@ -0,0 +1,55 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The BookmarkRepository. + /// + public class BookmarkRepository : GenericRepository, IBookmarkRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public BookmarkRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The GetById. + /// + /// The bookmarkId. + /// The . + public async Task GetById(int bookmarkId) + { + return await DbContext.UserBookmark.SingleAsync(ub => ub.Id == bookmarkId); + } + + /// + /// DeleteFolder. + /// + /// bookmarkId. + /// userId. + /// A representing the result of the asynchronous operation. + public async Task DeleteFolder(int bookmarkId, int userId) + { + var bookmarks = DbContext.UserBookmark.Where(ub => ub.Id == bookmarkId || ub.ParentId == bookmarkId); + + foreach (var bookmark in bookmarks) + { + bookmark.ParentId = null; + bookmark.Deleted = true; + SetAuditFieldsForUpdate(userId, bookmark); + } + + await DbContext.SaveChangesAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EmailChangeValidationTokenRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EmailChangeValidationTokenRepository.cs new file mode 100644 index 000000000..46156ee66 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/EmailChangeValidationTokenRepository.cs @@ -0,0 +1,81 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The user password validation token repository. + /// + public class EmailChangeValidationTokenRepository : GenericRepository, IEmailChangeValidationTokenRepository + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The db context. + /// + /// + /// The Timezone offset manager. + /// + public EmailChangeValidationTokenRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The expire email change validation token. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + public async Task ExpireEmailChangeValidationToken(string lookup) + { + var upvt = await GetByToken(lookup); + DbContext.EmailChangeValidationToken.Remove(upvt); + DbContext.SaveChanges(); + } + + /// + /// The get by token. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + public async Task GetByToken(string lookup) + { + return await DbContext.EmailChangeValidationToken + .Include(vt => vt.User) + .Where(vt => vt.Lookup == lookup) + .FirstOrDefaultAsync(); + } + + /// + /// The get by token. + /// + /// + /// The lookup. + /// + /// + /// The . + /// + public async Task GetLastIssuedEmailChangeValidationToken(int userId) + { + var validationToken = await DbContext.EmailChangeValidationToken + .Where(vt => vt.UserId == userId).AsNoTracking() + .OrderByDescending(vt => vt.Id).FirstOrDefaultAsync(); + + return validationToken?.StatusId == (int)EmailChangeValidationTokenStatusEnum.Issued ? validationToken : null; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs new file mode 100644 index 000000000..cff356d60 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs @@ -0,0 +1,177 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The generic repository. + /// + /// Input type. + public class GenericRepository : IGenericRepository + where TEntity : EntityBase + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public GenericRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + { + DbContext = dbContext; + TimezoneOffsetManager = tzOffsetManager; + } + + /// + /// Gets the db context. + /// + protected LearningHubDbContext DbContext { get; } + + /// + /// Gets the TimezoneOffset manager. + /// + protected ITimezoneOffsetManager TimezoneOffsetManager { get; } + + /// + /// The get all. + /// + /// The . + public IQueryable GetAll() + { + return DbContext.Set().AsNoTracking(); + } + + /// + /// The create async. + /// + /// The user id. + /// The entity. + /// The . + public virtual async Task CreateAsync(int userId, TEntity entity) + { + await DbContext.Set().AddAsync(entity); + SetAuditFieldsForCreate(userId, entity); + try + { + await DbContext.SaveChangesAsync(); + } + catch (Exception) + { + throw; + } + + DbContext.Entry(entity).State = EntityState.Detached; + + return entity.Id; + } + + /// + /// The update async. + /// + /// The user id. + /// The entity. + /// The . + public virtual async Task UpdateAsync(int userId, TEntity entity) + { + DbContext.Set().Update(entity); + + SetAuditFieldsForUpdate(userId, entity); + + await DbContext.SaveChangesAsync(); + + DbContext.Entry(entity).State = EntityState.Detached; + } + + /// + /// The update. + /// + /// The user id. + /// The entity. + public virtual void Update(int userId, TEntity entity) + { + DbContext.Set().Update(entity); + + SetAuditFieldsForUpdate(userId, entity); + + DbContext.SaveChanges(); + + DbContext.Entry(entity).State = EntityState.Detached; + } + + /// + /// The set audit fields for create. + /// + /// The user id. + /// The entity. + public void SetAuditFieldsForCreate(int userId, EntityBase entity) + { + var amendDate = GetAmendDate(); + + entity.Deleted = false; + entity.CreateUserId = userId; + entity.CreateDate = amendDate; + entity.AmendUserId = userId; + entity.AmendDate = amendDate; + } + + /// + /// The set audit fields for create or delete. + /// + /// The user id. + /// The entity. + /// Flag used to specify the journey. + public void SetAuditFieldsForCreateOrDelete(int userId, EntityBase entity, bool isCreate) + { + if (isCreate) + { + SetAuditFieldsForCreate(userId, entity); + } + else + { + SetAuditFieldsForDelete(userId, entity); + } + } + + /// + /// The set audit fields for update. + /// + /// The user id. + /// The entity. + public void SetAuditFieldsForUpdate(int userId, EntityBase entity) + { + entity.AmendUserId = userId; + entity.AmendDate = GetAmendDate(); + DbContext.Entry(entity).Property("CreateUserId").IsModified = false; + DbContext.Entry(entity).Property("CreateDate").IsModified = false; + if (entity.GetType() == typeof(User)) + { + DbContext.Entry(entity).Property("VersionStartTime").IsModified = false; + DbContext.Entry(entity).Property("VersionEndTime").IsModified = false; + } + } + + /// + /// The set audit fields for delete. + /// + /// The user id. + /// The entity. + public void SetAuditFieldsForDelete(int userId, EntityBase entity) + { + entity.Deleted = true; + entity.AmendUserId = userId; + entity.AmendDate = GetAmendDate(); + DbContext.Entry(entity).Property("CreateUserId").IsModified = false; + DbContext.Entry(entity).Property("CreateDate").IsModified = false; + } + + private DateTimeOffset GetAmendDate() + { + var tzOffset = TimezoneOffsetManager.UserTimezoneOffset; + return tzOffset.HasValue ? new DateTimeOffset(DateTime.UtcNow.AddMinutes(tzOffset.Value).Ticks, TimeSpan.FromMinutes(tzOffset.Value)) : DateTimeOffset.Now; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueAccessRequestRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueAccessRequestRepository.cs new file mode 100644 index 000000000..a3b5832ba --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueAccessRequestRepository.cs @@ -0,0 +1,79 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The CatalogueAccessRequestRepository class. + /// + public class CatalogueAccessRequestRepository : GenericRepository, ICatalogueAccessRequestRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public CatalogueAccessRequestRepository(LearningHubDbContext context, ITimezoneOffsetManager tzOffsetManager) + : base(context, tzOffsetManager) + { + } + + /// + /// The GetByUserIdAndCatalogueId. + /// + /// The catalogueId. + /// The userId. + /// The catalogueAccessRequest. + public CatalogueAccessRequest GetByUserIdAndCatalogueId(int catalogueNodeId, int userId) + { + return GetAll().OrderByDescending(x => x.CreateDate).FirstOrDefault(x => x.UserId == userId && x.CatalogueNodeId == catalogueNodeId); + } + + /// + /// The GetAllByUserIdAndCatalogueId. + /// + /// The catalogueId. + /// The userId. + /// The catalogueAccessRequest. + public IQueryable GetAllByUserIdAndCatalogueId(int catalogueNodeId, int userId) + { + return GetAll().Where(x => x.UserId == userId && x.CatalogueNodeId == catalogueNodeId).OrderByDescending(x => x.CreateDate); + } + + /// + /// The CreateCatalogueAccessRequestAsync. + /// + /// The currentUserId. + /// The reference. + /// The message. + /// The roleId. + /// The catalogueManageAccessUrl. + /// The accessType. + /// The task. + public async Task CreateCatalogueAccessRequestAsync( + int currentUserId, + string reference, + string message, + int roleId, + string catalogueManageAccessUrl, + string accessType) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = currentUserId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = reference }; + var param2 = new SqlParameter("@p2", SqlDbType.NVarChar) { Value = message }; + var param3 = new SqlParameter("@p3", SqlDbType.NVarChar) { Value = catalogueManageAccessUrl }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param5 = new SqlParameter("@p5", SqlDbType.NVarChar) { Value = accessType }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Value = roleId }; + await DbContext.Database.ExecuteSqlRawAsync("exec [hierarchy].[CatalogueAccessRequestCreate] @p0, @p1, @p2, @p3, @p4, @p5, @p6", param0, param1, param2, param3, param4, param5, param6); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs new file mode 100644 index 000000000..ee2dd0f50 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -0,0 +1,393 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The CatalogueNodeVersionRepository. + /// + public class CatalogueNodeVersionRepository : GenericRepository, ICatalogueNodeVersionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public CatalogueNodeVersionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get basic catalogue. + /// + /// The catalogueNodeId. + /// The . + public CatalogueNodeVersion GetBasicCatalogue(int catalogueNodeId) + { + var catalogue = DbContext.CatalogueNodeVersion.AsNoTracking() + .Include(cnv => cnv.NodeVersion.Node) + .FirstOrDefault(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published + && cnv.NodeVersion.NodeId == catalogueNodeId); + + return catalogue; + } + + /// + /// The get catalogues for id list. + /// + /// The catalogues ids. + /// The . + public async Task> GetCatalogues(List catalogueIds) + { + return await DbContext.CatalogueNodeVersion.AsNoTracking() + .Include(r => r.NodeVersion).AsNoTracking() + .Include(r => r.NodeVersion.Node).AsNoTracking() + .Where(r => !r.Deleted + && catalogueIds.Any(catalogueId => catalogueId == r.NodeVersion.NodeId) + && r.NodeVersion.Node.CurrentNodeVersionId == r.NodeVersionId + && r.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published).ToListAsync(); + } + + /// + /// The get catalogues. + /// + /// The . + public async Task> GetPublishedCatalogues() + { + return await DbContext.CatalogueNodeVersion.AsNoTracking() + .Include(r => r.NodeVersion).AsNoTracking() + .Include(r => r.NodeVersion.Node).AsNoTracking() + .Where(r => !r.Deleted + && r.NodeVersion.Node.CurrentNodeVersionId == r.NodeVersionId + && r.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published).ToListAsync(); + } + + /// + /// The get published catalogues for user. + /// + /// The user id. + /// The . + public IQueryable GetPublishedCataloguesForUserAsync(int userId) + { + var communityCatalogue = DbContext.CatalogueNodeVersion.AsNoTracking() + .Include(cnv => cnv.NodeVersion.Node) + .Where(cnv => cnv.NodeVersion.VersionStatusEnum == VersionStatusEnum.Published + && cnv.NodeVersion.NodeId == 1 /* Community Catalogue */); + + var cataloguesForUser = from cnv in DbContext.CatalogueNodeVersion.Include(cnv => cnv.NodeVersion.Node).AsNoTracking() + join nv in DbContext.NodeVersion.Where(cnv => cnv.VersionStatusEnum == VersionStatusEnum.Published && !cnv.Deleted) // .Include(nv => nv.Node) + on cnv.NodeVersionId equals nv.Id + join s in DbContext.Scope.Where(x => !x.Deleted) + on nv.NodeId equals s.CatalogueNodeId + join rug in DbContext.RoleUserGroup.Where(r => r.RoleId == (int)RoleEnum.Editor && !r.Deleted) + on s.Id equals rug.ScopeId + join uug in DbContext.UserUserGroup.Where(u => u.UserId == userId && !u.Deleted) + on rug.UserGroupId equals uug.UserGroupId + join n in DbContext.Node.Where(x => !x.Deleted) + on nv.Id equals n.CurrentNodeVersionId + select cnv; + + var returnedCatalogues = communityCatalogue.Union(cataloguesForUser).Distinct() + .OrderBy(cnv => cnv.NodeVersion.NodeId != 1) + .ThenBy(cnv => cnv.Name); + + return returnedCatalogues; + } + + /// + /// The CreateCatalogueAsync. + /// + /// The userId. + /// The catalogue view model. + /// The catalogueNodeVersionId. + public async Task CreateCatalogueAsync(int userId, CatalogueViewModel vm) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = (int)vm.Status }; + var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = vm.Name }; + var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = vm.Url }; + var param3 = string.IsNullOrEmpty(vm.BadgeUrl) + ? new SqlParameter("@p3", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p3", SqlDbType.VarChar) { Value = vm.BadgeUrl }; + var param4 = string.IsNullOrEmpty(vm.CertificateUrl) + ? new SqlParameter("@p4", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p4", SqlDbType.VarChar) { Value = vm.CertificateUrl }; + var param5 = string.IsNullOrEmpty(vm.CardImageUrl) + ? new SqlParameter("@p5", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p5", SqlDbType.VarChar) { Value = vm.CardImageUrl }; + var param6 = string.IsNullOrEmpty(vm.BannerUrl) + ? new SqlParameter("@p6", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p6", SqlDbType.VarChar) { Value = vm.BannerUrl }; + var param7 = new SqlParameter("@p7", SqlDbType.Int) { Value = (int)vm.ResourceOrder }; + var param8 = new SqlParameter("@p8", SqlDbType.VarChar) { Value = vm.Description }; + var param9 = new SqlParameter("@p9", SqlDbType.Int) { Value = userId }; + var param10 = new SqlParameter("@p10", SqlDbType.Bit) { Value = vm.Hidden }; + var param11 = new SqlParameter("@p11", SqlDbType.VarChar) { Value = string.Join(",", vm.Keywords) }; + var param12 = new SqlParameter("@p12", SqlDbType.Bit) { Value = vm.RestrictedAccess }; + var param13 = new SqlParameter("@p13", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param14 = new SqlParameter("@p14", SqlDbType.Int) { Value = vm.CatalogueNodeVersionProvider.ProviderId }; + var param15 = new SqlParameter("@p15", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + await DbContext.Database.ExecuteSqlRawAsync("hierarchy.CatalogueCreate @p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15 output", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15); + + int catalogueNodeVersionId = (int)param15.Value; + + return catalogueNodeVersionId; + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// The UpdateCatalogueAsync. + /// + /// The userId. + /// The catalogue view model. + /// The task. + public async Task UpdateCatalogueAsync(int userId, CatalogueViewModel vm) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = (int)vm.Status }; + var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = vm.Name }; + var param2 = string.IsNullOrEmpty(vm.BadgeUrl) + ? new SqlParameter("@p2", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p2", SqlDbType.VarChar) { Value = vm.BadgeUrl }; + var param3 = string.IsNullOrEmpty(vm.CardImageUrl) + ? new SqlParameter("@p3", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p3", SqlDbType.VarChar) { Value = vm.CardImageUrl }; + var param4 = string.IsNullOrEmpty(vm.BannerUrl) + ? new SqlParameter("@p4", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p4", SqlDbType.VarChar) { Value = vm.BannerUrl }; + var param5 = string.IsNullOrEmpty(vm.CertificateUrl) + ? new SqlParameter("@p5", SqlDbType.VarChar) { Value = DBNull.Value } + : new SqlParameter("@p5", SqlDbType.VarChar) { Value = vm.CertificateUrl }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Value = (int)vm.ResourceOrder }; + var param7 = new SqlParameter("@p7", SqlDbType.VarChar) { Value = vm.Description }; + var param8 = new SqlParameter("@p8", SqlDbType.Int) { Value = userId }; + var param9 = new SqlParameter("@p9", SqlDbType.VarChar) { Value = string.Join(",", vm.Keywords) }; + var param10 = new SqlParameter("@p10", SqlDbType.Int) { Value = vm.CatalogueNodeVersionId }; + var param11 = new SqlParameter("@p11", SqlDbType.Bit) { Value = vm.RestrictedAccess }; + var param12 = new SqlParameter("@p12", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param13 = new SqlParameter("@p13", SqlDbType.Int) { Value = vm.CatalogueNodeVersionProvider.ProviderId }; + + await DbContext.Database.ExecuteSqlRawAsync("hierarchy.CatalogueUpdate @p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// The UpdateCatalogueOwnerAsync. + /// + /// The userId. + /// The catalogue owner view model. + /// The task. + public async Task UpdateCatalogueOwnerAsync(int userId, CatalogueOwnerViewModel vm) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.VarChar) { Value = vm.OwnerName }; + var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = vm.OwnerEmailAddress }; + var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = vm.Notes }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = vm.CatalogueNodeVersionId }; + var param5 = new SqlParameter("@p5", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("hierarchy.CatalogueOwnerUpdate @p0, @p1, @p2, @p3, @p4, @p5", param0, param1, param2, param3, param4, param5); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// Get Catlogue by reference. + /// + /// The reference. + /// The CatalogueViewModel. + public async Task GetCatalogueAsync(string reference) + { + return await (from cnv in DbContext.CatalogueNodeVersion.AsNoTracking() + join nv in DbContext.NodeVersion.AsNoTracking() on cnv.NodeVersionId equals nv.Id + join n in DbContext.Node.AsNoTracking() on cnv.NodeVersionId equals n.CurrentNodeVersionId + where cnv.Url != null && cnv.Url.ToLower() == reference.ToLower() && cnv.Deleted == false && nv.VersionStatusEnum == VersionStatusEnum.Published + select new CatalogueViewModel + { + Id = cnv.Id, + NodeVersionId = cnv.NodeVersionId, + BadgeUrl = cnv.BadgeUrl, + BannerUrl = cnv.BannerUrl, + CertificateUrl = cnv.CertificateUrl, + Name = cnv.Name, + Description = cnv.Description, + Notes = cnv.Notes, + Url = cnv.Url, + NodeId = n.Id, + CatalogueNodeVersionId = cnv.NodeVersionId, + ResourceOrder = cnv.Order, + RestrictedAccess = cnv.RestrictedAccess, + Hidden = n.Hidden, + }).SingleOrDefaultAsync(); + } + + /// + /// Get list of Restricted Catalogue AccessRequests for the supplied request. + /// + /// The restrictedCatalogueAccessRequestsRequestViewModel. + /// A RestrictedCatalogueAccessRequestsViewModel. + public List GetRestrictedCatalogueAccessRequests(RestrictedCatalogueAccessRequestsRequestViewModel restrictedCatalogueAccessRequestsRequestViewModel) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = restrictedCatalogueAccessRequestsRequestViewModel.CatalogueNodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Bit) { Value = restrictedCatalogueAccessRequestsRequestViewModel.IncludeNew }; + var param2 = new SqlParameter("@p2", SqlDbType.Bit) { Value = restrictedCatalogueAccessRequestsRequestViewModel.IncludeApproved }; + var param3 = new SqlParameter("@p3", SqlDbType.Bit) { Value = restrictedCatalogueAccessRequestsRequestViewModel.IncludeDenied }; + + var restrictedCatalogueAccessRequests = DbContext.RestrictedCatalogueAccessRequestViewModel.FromSqlRaw("hub.RestrictedCatalogueGetAccessRequests @p0, @p1, @p2, @p3", param0, param1, param2, param3).ToList(); + + return restrictedCatalogueAccessRequests; + } + + /// + /// Get list of Restricted Catalogue Users for the supplied request. + /// + /// The restrictedCatalogueUsersRequestViewModel. + /// A RestrictedCatalogueUsersViewModel. + public RestrictedCatalogueUsersViewModel GetRestrictedCatalogueUsers(RestrictedCatalogueUsersRequestViewModel restrictedCatalogueUsersRequestViewModel) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = restrictedCatalogueUsersRequestViewModel.CatalogueNodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = string.IsNullOrEmpty(restrictedCatalogueUsersRequestViewModel.EmailAddressFilter) ? string.Empty : restrictedCatalogueUsersRequestViewModel.EmailAddressFilter }; + var param2 = new SqlParameter("@p2", SqlDbType.Bit) { Value = restrictedCatalogueUsersRequestViewModel.IncludeCatalogueAdmins }; + var param3 = new SqlParameter("@p3", SqlDbType.Bit) { Value = restrictedCatalogueUsersRequestViewModel.IncludePlatformAdmins }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = restrictedCatalogueUsersRequestViewModel.Skip }; + var param5 = new SqlParameter("@p5", SqlDbType.Int) { Value = restrictedCatalogueUsersRequestViewModel.Take }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + var restrictedCatalogueUsers = DbContext.RestrictedCatalogueUserViewModel.FromSqlRaw("hub.RestrictedCatalogueGetUsers @p0, @p1, @p2, @p3, @p4, @p5, @p6 output", param0, param1, param2, param3, param4, param5, param6).ToList(); + + var vm = new RestrictedCatalogueUsersViewModel(); + vm.RestrictedCatalogueUsers = restrictedCatalogueUsers; + vm.UserCount = (int)param6.Value; + + return vm; + } + + /// + /// Get Restricted Catalogue Summary for the supplied catalogue node id. + /// + /// The catalogueNodeId. + /// A RestrictedCatalogueUsersViewModel. + public RestrictedCatalogueSummaryViewModel GetRestrictedCatalogueSummary(int catalogueNodeId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = catalogueNodeId }; + + var vm = DbContext.RestrictedCatalogueSummaryViewModel.FromSqlRaw("hub.RestrictedCatalogueGetSummary @p0", param0).AsEnumerable().FirstOrDefault(); + + return vm; + } + + /// + /// Gets catalogues for dashboard based on type. + /// + /// The dashboard type. + /// The page Number. + /// The userId. + /// resources. + public (int TotalCount, List Catalogues) GetCatalogues(string dashboardType, int pageNumber, int userId) + { + var param0 = new SqlParameter("@DashboardType", SqlDbType.NVarChar, 30) { Value = dashboardType }; + var param1 = new SqlParameter("@UserId", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@pageNumber", SqlDbType.Int) { Value = pageNumber }; + var param3 = new SqlParameter("@TotalRecords", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + var dashboardCatalogues = DbContext.DashboardCatalogueDto.FromSqlRaw("[hierarchy].[GetDashboardCatalogues] @DashboardType, @UserId, @pageNumber, @TotalRecords OUTPUT", param0, param1, param2, param3).ToList(); + + return (TotalCount: (int)param3.Value, Catalogues: dashboardCatalogues); + } + + /// + public async Task ShowCatalogue(int userId, int nodeId) + { + var param0 = new SqlParameter("@NodeId", SqlDbType.Int) { Value = nodeId }; + var param1 = new SqlParameter("@UserId", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@UserTimezoneOffset", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("[hierarchy].[CatalogueShow] @NodeId, @UserId, @UserTimezoneOffset", param0, param1, param2); + } + + /// + /// Check if a Catalogue with a specific name exists or not. + /// + /// The catalogue name. + /// True if the catalogue exists, otherwise false. + public async Task ExistsAsync(string name) + { + return await DbContext.CatalogueNodeVersion.AnyAsync(x => x.Name.Equals(name) && !x.Deleted); + } + + /// + /// Gets the Node Id for a particular catalogue name. + /// + /// The catalogue name. + /// The catalogue's node id. + public async Task GetNodeIdByCatalogueName(string catalogueName) + { + return await (from cnv in DbContext.CatalogueNodeVersion.AsNoTracking() + join nv in DbContext.NodeVersion.AsNoTracking() on cnv.NodeVersionId equals nv.Id + where cnv.Name == catalogueName && cnv.Deleted == false + select nv.NodeId).FirstOrDefaultAsync(); + } + + /// + /// Gets catalogues count based on alphabets. + /// + /// The userId. + /// resources. + public List GetAllCataloguesAlphaCount(int userId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + + var result = DbContext.AllCatalogueAlphabetModel.FromSqlRaw("[hierarchy].[GetCataloguesCount] @userid", param0) + .AsNoTracking().ToList(); + return result; + } + + /// + /// Gets catalogues based on filter character. + /// + /// The pageSize. + /// The filterChar. + /// The userId. + /// resources. + public async Task> GetAllCataloguesAsync(int pageSize, string filterChar, int userId) + { + var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; + var param1 = new SqlParameter("@filterChar", SqlDbType.NVarChar, 10) { Value = filterChar.Trim() }; + var param2 = new SqlParameter("@OffsetRows", SqlDbType.Int) { Value = 0 }; + var param3 = new SqlParameter("@fetchRows", SqlDbType.Int) { Value = pageSize }; + + var result = await DbContext.AllCatalogueViewModel.FromSqlRaw("[hierarchy].[GetCatalogues] @userId, @filterChar, @OffsetRows, @fetchRows", param0, param1, param2, param3) + .AsNoTracking().ToListAsync(); + return result; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/FolderNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/FolderNodeVersionRepository.cs new file mode 100644 index 000000000..450d07463 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/FolderNodeVersionRepository.cs @@ -0,0 +1,40 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.EntityFrameworkCore; + + /// + /// The FolderNodeVersionRepository. + /// + public class FolderNodeVersionRepository : GenericRepository, IFolderNodeVersionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public FolderNodeVersionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The GetFolder. + /// + /// The node version id. + /// The folder node version. + public async Task GetFolderAsync(int nodeVersionId) + { + var folder = await DbContext.FolderNodeVersion.AsNoTracking() + .Include(f => f.NodeVersion) + .ThenInclude(f => f.Node) + .FirstOrDefaultAsync(f => f.NodeVersionId == nodeVersionId); + + return folder; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditDetailRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditDetailRepository.cs new file mode 100644 index 000000000..5ce9ebbf2 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditDetailRepository.cs @@ -0,0 +1,44 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.EntityFrameworkCore; + + /// + /// The HierarchyEditDetail repository. + /// + public class HierarchyEditDetailRepository : GenericRepository, IHierarchyEditDetailRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public HierarchyEditDetailRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get root hierarchy detail by hierarchy edit id async. + /// + /// The hierarchy edit id. + /// The node id. + /// The . + public async Task GetByNodeIdAsync(long hierarchyEditId, int nodeId) + { + try + { + var retVal = await DbContext.HierarchyEditDetail.AsNoTracking().FirstOrDefaultAsync(r => r.HierarchyEditId == hierarchyEditId && r.NodeId == nodeId && !r.Deleted); + return retVal; + } + catch + { + throw; + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditRepository.cs new file mode 100644 index 000000000..9fb197f09 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/HierarchyEditRepository.cs @@ -0,0 +1,393 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The HierarchyEdit repository. + /// + public class HierarchyEditRepository : GenericRepository, IHierarchyEditRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public HierarchyEditRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.HierarchyEdit.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + + /// + /// The get by root node id async. + /// + /// The id. + /// The . + public async Task> GetByRootNodeIdAsync(int rootNodeId) + { + return await DbContext.HierarchyEdit.AsNoTracking() + .Include(x => x.Publication) + .Include(x => x.CreateUser) + .Where(x => x.RootNodeId == rootNodeId && !x.Deleted).ToListAsync(); + } + + /// + /// The create. + /// + /// The root node id. + /// The user id. + /// The hierarchy edit id. + /// The . + public async Task Create(int rootNodeId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = rootNodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + string sql = "hierarchy.HierarchyEditCreate @p0, @p1, @p2, @p3 output"; + var sqlParams = new List() { param0, param1, param2, param3 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + + return (int)param3.Value; + } + + /// + /// The discard. + /// + /// The hierarchy edit id. + /// The user id. + /// The . + public async Task Discard(int hierarchyEditId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditDiscard @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Creates a new folder. + /// + /// The folderEditViewModel. + /// The user id. + /// The . + public async Task CreateFolder(FolderEditViewModel folderEditViewModel, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = folderEditViewModel.HierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = folderEditViewModel.Name }; + var param2 = new SqlParameter("@p2", SqlDbType.NVarChar) { Value = folderEditViewModel.Description }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = folderEditViewModel.ParentNodeId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = userId }; + var param5 = new SqlParameter("@p5", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + string sql = "hierarchy.HierarchyEditFolderCreate @p0, @p1, @p2, @p3, @p4, @p5, @p6 output"; + var sqlParams = new List() { param0, param1, param2, param3, param4, param5, param6 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + + return (int)param6.Value; + } + + /// + /// Updates a folder. + /// + /// The folderEditViewModel. + /// The user id. + /// The . + public async Task UpdateFolder(FolderEditViewModel folderEditViewModel, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = folderEditViewModel.HierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = folderEditViewModel.Name }; + var param2 = new SqlParameter("@p2", SqlDbType.NVarChar) { Value = folderEditViewModel.Description }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditFolderUpdate @p0, @p1, @p2, @p3, @p4"; + var sqlParams = new List() { param0, param1, param2, param3, param4 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Deletes a folder. + /// + /// The id. + /// The user id. + /// The . + public async Task DeleteFolder(int hierarchyEditDetailId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditDeleteFolder @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a node up. + /// + /// The id. + /// The user id. + /// The . + public async Task MoveNodeUp(int hierarchyEditDetailId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveNodeUp @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a node down. + /// + /// The id. + /// The user id. + /// The . + public async Task MoveNodeDown(int hierarchyEditDetailId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveNodeDown @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a node. + /// + /// The moveNodeViewModel . + /// The user id. + /// The . + public async Task MoveNode(MoveNodeViewModel moveNodeViewModel, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = moveNodeViewModel.HierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = moveNodeViewModel.MoveToHierarchyEditDetailId }; + var param3 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveNode @p0, @p1, @p2, @p3"; + var sqlParams = new List() { param0, param1, param2, param3 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource up in a hierarchy edit. + /// + /// The id. + /// The user id. + /// The . + public async Task HierarchyEditMoveResourceUp(int hierarchyEditDetailId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveResourceUp @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource down in a hierarchy edit. + /// + /// The id. + /// The user id. + /// The . + public async Task HierarchyEditMoveResourceDown(int hierarchyEditDetailId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveResourceDown @p0, @p1, @p2"; + var sqlParams = new List() { param0, param1, param2 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource in a HierarchyEdit. + /// + /// The view model . + /// The user id. + /// The . + public async Task HierarchyEditMoveResource(HierarchyEditMoveResourceViewModel moveResourceViewModel, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = moveResourceViewModel.HierarchyEditDetailId }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = moveResourceViewModel.MoveToHierarchyEditDetailId }; + var param3 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.HierarchyEditMoveResource @p0, @p1, @p2, @p3"; + var sqlParams = new List() { param0, param1, param2, param3 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource up. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node containing the resource. + /// The resource id. + /// The user id. + /// The . + public async Task MoveResourceUp(int nodeId, int resourceId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.MoveResourceUp @p0, @p1, @p2, @p3"; + var sqlParams = new List() { param0, param1, param2, param3 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource down. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node containing the resource. + /// The resource id. + /// The user id. + /// The . + public async Task MoveResourceDown(int nodeId, int resourceId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + string sql = "hierarchy.MoveResourceDown @p0, @p1, @p2, @p3"; + var sqlParams = new List() { param0, param1, param2, param3 }; + + await DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); + } + + /// + /// Moves a resource. + /// ITERATION 1 - Not moved within a hierarchy edit, update happens immediately. + /// + /// The id of the node to move the resource from. + /// The id of the node to move the resource to. + /// The resource id. + /// The user id. + /// A list of nodeIds affected by the moved resource. The nodes that will need to be refreshed in the UI. + public async Task> MoveResource(int sourceNodeId, int destinationNodeId, int resourceId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = sourceNodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = destinationNodeId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = resourceId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + var result = await DbContext.MoveResourceResultViewModel.FromSqlRaw("hierarchy.MoveResource @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4).AsNoTracking().ToListAsync(); + return result; + } + + /// + /// The publishing. + /// + /// The hierarchy edit id. + /// The user id. + public void SubmitForPublishing(int hierarchyEditId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + DbContext.Database.ExecuteSqlRaw("hierarchy.HierarchyEditSubmitForPublishing @p0, @p1, @p2", param0, param1, param2); + } + + /// + /// The publish. + /// + /// The hierarchy edit id. + /// The is major revision. + /// The notes. + /// The user id. + /// The publication id. + public int Publish(int hierarchyEditId, bool isMajorRevision, string notes, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.Bit) { Value = isMajorRevision }; + var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = notes }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + string sql = "hierarchy.HierarchyEditPublish @p0, @p1, @p2, @p3, @p4 output"; + var sqlParams = new List() { param0, param1, param2, param3, param4 }; + + DbContext.Database.ExecuteSqlRaw(sql, sqlParams); + + return (int)param4.Value; + } + + /// + /// Set hierarchy edit to "publishing". + /// + /// The hierarchy edit id. + /// The user id. + public void Publishing(int hierarchyEditId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + + DbContext.Database.ExecuteSqlRaw("hierarchy.HierarchyEditPublishing @p0, @p1", param0, param1); + } + + /// + /// Set hierarchy edit to "failed to publish". + /// + /// The hierarchy edit id. + /// The user id. + public void FailedToPublish(int hierarchyEditId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = hierarchyEditId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + + DbContext.Database.ExecuteSqlRaw("hierarchy.HierarchyEditFailedToPublish @p0, @p1", param0, param1); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodePathRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodePathRepository.cs new file mode 100644 index 000000000..003a08a85 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodePathRepository.cs @@ -0,0 +1,69 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The NodePathRepository. + /// + public class NodePathRepository : GenericRepository, INodePathRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbContext. + /// The Timezone offset manager. + public NodePathRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// Gets the root catalogue nodeId for any given nodeId (i.e. folder/course). + /// + /// The folder/course nodeId. + /// The catalogue nodeId. + public async Task GetCatalogueRootNodeId(int folderNodeId) + { + return await DbContext.NodePath.AsNoTracking() + .Where(np => np.NodeId == folderNodeId + && np.Deleted == false) + .Select(np => np.CatalogueNodeId) + .FirstAsync(); + } + + /// + /// Gets the node paths to the supplied node id. + /// + /// The nodeId. + /// The list of NodePaths. + public async Task> GetNodePathsForNodeId(int nodeId) + { + return await DbContext.NodePath.AsNoTracking() + .Where(np => np.NodeId == nodeId) + .ToListAsync(); + } + + /// + /// Gets the basic details of all Nodes in a particular NodePath. + /// + /// The NodePath id. + /// The . + public async Task> GetNodePathNodes(int nodePathId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodePathId }; + + var retVal = await DbContext.NodePathNodeViewModel.FromSqlRaw("hierarchy.GetNodePathNodes @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeRepository.cs new file mode 100644 index 000000000..d5146fa66 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeRepository.cs @@ -0,0 +1,120 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The node repository. + /// + public class NodeRepository : GenericRepository, INodeRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public NodeRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.Node.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + + /// + /// Gets the basic details of a node. Currently catalogues or folders. + /// + /// The node id. + /// The node details. + public async Task GetNodeDetails(int nodeId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + + var retVal = await DbContext.NodeViewModel.FromSqlRaw("hierarchy.GetNodeDetails @p0", param0).AsNoTracking().ToListAsync(); + NodeViewModel nodeViewModel = retVal.FirstOrDefault(); + return nodeViewModel; + } + + /// + /// Gets the contents of a node for the catalogue landing page - i.e. published folders and published resources only. + /// Only returns the items found directly in the specified node, does not recurse down through subfolders. + /// + /// The node id. + /// Include Empty Folder or not. + /// The . + public async Task> GetNodeContentsForCatalogueBrowse(int nodeId, bool includeEmptyFolder) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + if (includeEmptyFolder) + { + var retVal = await DbContext.NodeContentBrowseViewModel.FromSqlRaw("hierarchy.GetNodeContentsForCatalogueBrowse_withEmptyFolders @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + else + { + var retVal = await DbContext.NodeContentBrowseViewModel.FromSqlRaw("hierarchy.GetNodeContentsForCatalogueBrowse @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + } + + /// + /// Gets the contents of a node for the My Contributions page - i.e. published folders only, and all resources (i.e. all statuses). + /// Only returns the items found directly in the specified node, does not recurse down through subfolders. + /// + /// The node id. + /// The . + public async Task> GetNodeContentsForCatalogueEditor(int nodeId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + + var retVal = await DbContext.NodeContentEditorViewModel.FromSqlRaw("hierarchy.GetNodeContentsForCatalogueEditor @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + + /// + /// Gets the contents of a node (catalogue/folder/course) - i.e. returns a list of subfolders and resources. Only returns the + /// items from the first level down. Doesn't recurse through subfolders. + /// + /// The node id. + /// Set to true if read only data set is required. + /// The . + public async Task> GetNodeContentsAdminAsync(int nodeId, bool readOnly) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + + if (readOnly) + { + var retVal = await DbContext.NodeContentAdminViewModel.FromSqlRaw("hierarchy.GetNodeContentsAdminReadOnly @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + else + { + var retVal = await DbContext.NodeContentAdminViewModel.FromSqlRaw("hierarchy.GetNodeContentsAdmin @p0", param0).AsNoTracking().ToListAsync(); + return retVal; + } + } + catch + { + throw; + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceLookupRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceLookupRepository.cs new file mode 100644 index 000000000..9e98b6eb3 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceLookupRepository.cs @@ -0,0 +1,39 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.EntityFrameworkCore; + + /// + /// The node resource repository. + /// + public class NodeResourceLookupRepository : GenericRepository, INodeResourceLookupRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public NodeResourceLookupRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by node id async. + /// + /// The node id. + /// The . + public async Task> GetByNodeIdAsync(int nodeId) + { + return await DbContext.NodeResourceLookup.AsNoTracking() + .Where(nrl => nrl.NodeId == nodeId) + .ToListAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceRepository.cs new file mode 100644 index 000000000..e881cb3dc --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/NodeResourceRepository.cs @@ -0,0 +1,161 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Helpers; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The node resource repository. + /// + public class NodeResourceRepository : GenericRepository, INodeResourceRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public NodeResourceRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by resource id async. + /// + /// The resource id. + /// The . + public async Task> GetByResourceIdAsync(int resourceId) + { + return await DbContext.NodeResource.AsNoTracking().Where(r => r.ResourceId == resourceId && !r.Deleted) + .Include(n => n.Node) + .ThenInclude(n => n.NodePaths) + .ThenInclude(n => n.NodePathNode) + .ThenInclude(n => n.Node) + .ToListAsync(); + } + + /// + /// Gets the Id of the node where a resource is currently located (IT1 - one resource is located in one node). + /// + /// The resource id. + /// The node id. + public async Task GetNodeIdByResourceId(int resourceId) + { + return await DbContext.NodeResource.AsNoTracking().Where(r => r.ResourceId == resourceId && !r.Deleted) + .Select(x => x.NodeId).SingleAsync(); + } + + /// + /// The get catalogue locations for resource. + /// + /// The resource id. + /// The CatalogueLocationViewModel list. + public List GetCatalogueLocationsForResource(int resourceId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = resourceId }; + + var vms = DbContext.CatalogueLocationViewModel.FromSqlRaw("hierarchy.CatalogueLocationsForResource @p0", param0).ToList(); + + return vms; + } + + /// + /// GetResourcesAsync. + /// + /// nodeId. + /// catalogueOrder. + /// offset. + /// CatalogueResourceViewModel. + public async Task GetResourcesAsync(int nodeId, CatalogueOrder catalogueOrder, int offset) + { + var resourceViewModels = (from nr in DbContext.NodeResource.AsNoTracking() + join rr in DbContext.ResourceReference.AsNoTracking() on nr.ResourceId equals rr.ResourceId + join np in DbContext.NodePath.AsNoTracking() on rr.NodePathId equals np.Id + join r in DbContext.Resource.AsNoTracking() on nr.ResourceId equals r.Id + join rv in DbContext.ResourceVersion.AsNoTracking() on r.CurrentResourceVersionId equals rv.Id + join grv in DbContext.GenericFileResourceVersion.AsNoTracking() on rv.Id equals grv.ResourceVersionId into genericjoin + from grv in genericjoin.DefaultIfEmpty() + where nr.NodeId == nodeId && np.NodeId == nodeId && rv.VersionStatusEnum == VersionStatusEnum.Published && rv.Deleted == false && nr.VersionStatusEnum == VersionStatusEnum.Published && nr.Deleted == false + select new CatalogueResourceViewModel + { + Type = r.ResourceTypeEnum.ToString(), + ResourceId = nr.ResourceId.ToString(), + ResourceVersionId = r.CurrentResourceVersionId.ToString(), + ResourceReferenceId = rr.Id.ToString(), + Title = rv.Title, + Description = rv.Description, + AuthoredBy = rv.ResourceVersionAuthor.FirstOrDefault().AuthorName, + Organisation = rv.ResourceVersionAuthor.FirstOrDefault().Organisation, + CreatedOn = rv.CreateDate.Date, + AuthoredDateText = r.ResourceTypeEnum != ResourceTypeEnum.GenericFile ? string.Empty : TextHelper.CombineDateComponents(grv.AuthoredYear, grv.AuthoredMonth, grv.AuthoredDayOfMonth), + }).AsQueryable(); + + if (resourceViewModels != null) + { + var totalResources = await resourceViewModels.CountAsync(); + if (catalogueOrder == CatalogueOrder.AlphabeticalAscending) + { + resourceViewModels = resourceViewModels.OrderBy(r => r.Title).Take(offset + 10); + } + else + { + resourceViewModels = resourceViewModels.OrderByDescending(r => r.CreatedOn).Take(offset + 10); + } + + return new CatalogueResourceResponseViewModel + { + NodeId = nodeId, + TotalResources = totalResources, + CatalogueResources = resourceViewModels?.ToList(), + }; + } + + return null; + } + + /// + /// Get All published resources id. + /// + /// The . + public async Task> GetAllPublishedResourceAsync() + { + return await (from nr in DbContext.NodeResource.AsNoTracking() + join rr in DbContext.ResourceReference.AsNoTracking() on nr.ResourceId equals rr.ResourceId + join np in DbContext.NodePath.AsNoTracking() on rr.NodePathId equals np.Id + join r in DbContext.Resource.AsNoTracking() on nr.ResourceId equals r.Id + join rv in DbContext.ResourceVersion.AsNoTracking() on r.CurrentResourceVersionId equals rv.Id + where nr.NodeId == np.NodeId && rv.VersionStatusEnum == VersionStatusEnum.Published && nr.VersionStatusEnum == VersionStatusEnum.Published + select rr.Id) + .ToListAsync(); + } + + /// + /// Creates or updates the NodeResource record for a draft resource in a node. + /// + /// The nodeId. + /// The resourceId. + /// The userId. + /// The . + public async Task CreateOrUpdateAsync(int nodeId, int resourceId, int userId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = nodeId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = resourceId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + return await DbContext.Database.ExecuteSqlRawAsync("hierarchy.NodeResourceCreateOrUpdate @p0, @p1, @p2, @p3", param0, param1, param2, param3); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/PublicationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/PublicationRepository.cs new file mode 100644 index 000000000..b4428f69f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/PublicationRepository.cs @@ -0,0 +1,88 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Hierarchy +{ + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The publication repository. + /// + public class PublicationRepository : GenericRepository, IPublicationRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public PublicationRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.Publication.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + + /// + /// The get by id. + /// + /// The id. + /// The Publication. + public Publication GetById(int id) + { + return DbContext.Publication.AsNoTracking().FirstOrDefault(r => r.Id == id && !r.Deleted); + } + + /// + /// The get by id. + /// + /// The resourceVersionId. + /// The Publication. + public async Task GetByResourceVersionIdAsync(int resourceVersionId) + { + return await DbContext.Publication.AsNoTracking().FirstOrDefaultAsync(r => r.ResourceVersionId == resourceVersionId && !r.Deleted); + } + + /// + /// Get cache operations for the supplied publication id. + /// + /// The publicationId. + /// A list of . + public async Task> GetCacheOperations(int publicationId) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = publicationId }; + + var vm = await DbContext.CacheOperationViewModel.FromSqlRaw("hierarchy.GetCacheRefreshDetails @p0", param0).ToListAsync(); + + return vm; + } + + /// + /// Get resource first publication record. + /// + /// resource id. + /// publish view model. + public async Task GetResourceFirstPublication(int resourceId) + { + return await (from pub in DbContext.Publication.AsNoTracking() + join rv in DbContext.ResourceVersion on pub.ResourceVersionId equals rv.Id + join r in DbContext.Resource on rv.ResourceId equals r.Id + where r.Id == resourceId + select pub).OrderBy(n => n.CreateDate).FirstOrDefaultAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Maintenance/InternalSystemRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Maintenance/InternalSystemRepository.cs new file mode 100644 index 000000000..ecb34c2c0 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Maintenance/InternalSystemRepository.cs @@ -0,0 +1,27 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Maintenance +{ + using LearningHub.Nhs.Models.Entities.Maintenance; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Maintenance; + + /// + /// The InternalSystemRepository. + /// + public class InternalSystemRepository : GenericRepository, IInternalSystemRepository + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The db context. + /// + /// + /// The Timezone offset manager. + /// + public InternalSystemRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/EmailTemplateRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/EmailTemplateRepository.cs new file mode 100644 index 000000000..079829892 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/EmailTemplateRepository.cs @@ -0,0 +1,37 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Messaging +{ + using System.Linq; + using LearningHub.Nhs.Models.Entities.Messaging; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Messaging; + using Microsoft.EntityFrameworkCore; + + /// + /// The EmailTemplateRepository. + /// + public class EmailTemplateRepository : GenericRepository, IEmailTemplateRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The Timezone offset manager. + public EmailTemplateRepository(LearningHubDbContext context, ITimezoneOffsetManager tzOffsetManager) + : base(context, tzOffsetManager) + { + } + + /// + /// The GetTemplate. + /// + /// The email template id. + /// The Email Template. + public EmailTemplate GetTemplate(int id) + { + return DbContext.EmailTemplate.AsNoTracking() + .Include(x => x.EmailTemplateLayout) + .SingleOrDefault(et => et.Id == id); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/MessageRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/MessageRepository.cs new file mode 100644 index 000000000..b8bc45779 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Messaging/MessageRepository.cs @@ -0,0 +1,181 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Messaging +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Messaging; + using LearningHub.Nhs.Models.Messaging; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Messaging; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The MessageRepository class. + /// + public class MessageRepository : GenericRepository, IMessageRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The Timezone offset manager. + public MessageRepository(LearningHubDbContext context, ITimezoneOffsetManager tzOffsetManager) + : base(context, tzOffsetManager) + { + } + + /// + /// The CreateEmailAsync. + /// + /// The user id. + /// The subject. + /// The body. + /// The recipient user id. + /// The task. + public async Task CreateEmailAsync(int userId, string subject, string body, int recipientUserId) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = subject }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = body }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = recipientUserId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("messaging.CreateEmailForUser @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// The CreateEmailAsync. + /// + /// The user id. + /// The subject. + /// The body. + /// The recipientEmailAddress. + /// The task. + public async Task CreateEmailAsync(int userId, string subject, string body, string recipientEmailAddress) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = subject }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = body }; + var param2 = new SqlParameter("@p2", SqlDbType.NVarChar) { Value = recipientEmailAddress }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("messaging.CreateEmailForEmailAddress @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// The CreateNotificationForUserAsync. + /// + /// The userId. + /// The subject. + /// The body. + /// The recipientUserId. + /// The notificationStartDate. + /// The notificationEndDate. + /// The notificationPriority. + /// The notificationType. + /// The task. + public async Task CreateNotificationForUserAsync( + int userId, + string subject, + string body, + int recipientUserId, + DateTimeOffset notificationStartDate, + DateTimeOffset notificationEndDate, + int notificationPriority, + int notificationType) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = subject }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = body }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = recipientUserId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.DateTimeOffset) { Value = notificationStartDate }; + var param5 = new SqlParameter("@p5", SqlDbType.DateTimeOffset) { Value = notificationEndDate }; + var param6 = new SqlParameter("@p6", SqlDbType.Int) { Value = notificationPriority }; + var param7 = new SqlParameter("@p7", SqlDbType.Int) { Value = notificationType }; + var param8 = new SqlParameter("@p8", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + + await DbContext.Database.ExecuteSqlRawAsync("messaging.CreateNotificationForUser @p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8", param0, param1, param2, param3, param4, param5, param6, param7, param8); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// Gets a list of all messages which have a message send which hasn't been sent. + /// + /// The messages. + public IQueryable GetPendingMessages() + { + // MarkAsSent = true + var param0 = new SqlParameter("@p0", SqlDbType.Bit) { Value = 1 }; + + return DbContext.FullMessageDto.FromSqlRaw("messaging.GetPendingMessages @p0", param0); + } + + /// + /// Marks a message send as having been successful. + /// + /// The userId. + /// The messageSends. + /// The task. + public async Task MessageSendSuccess(int userId, List messageSends) + { + try + { + var ids = string.Join(',', messageSends); + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = ids }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + + await DbContext.Database.ExecuteSqlRawAsync("messaging.MessageSendSuccess @p0, @p1", param0, param1); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// Either marks a message as failed, or queues it for a retry. + /// + /// The userId. + /// The messageSends. + /// The task. + public async Task MessageSendFailure(int userId, List messageSends) + { + try + { + var ids = string.Join(',', messageSends); + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = ids }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; + + await DbContext.Database.ExecuteSqlRawAsync("messaging.MessageSendFailed @p0, @p1", param0, param1); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Migrations/MigrationSourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Migrations/MigrationSourceRepository.cs new file mode 100644 index 000000000..91979a15f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Migrations/MigrationSourceRepository.cs @@ -0,0 +1,35 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Migrations +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Migration; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Migrations; + using Microsoft.EntityFrameworkCore; + + /// + /// The migration source repository. + /// + public class MigrationSourceRepository : GenericRepository, IMigrationSourceRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public MigrationSourceRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.MigrationSource.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/MultipleResultSets.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/MultipleResultSets.cs new file mode 100644 index 000000000..9889db659 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/MultipleResultSets.cs @@ -0,0 +1,132 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Data; + using System.Data.Common; + using System.Linq; + using System.Reflection; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// MultipleResultSets. + /// + public static class MultipleResultSets + { + /// + /// MultipleResults extension. + /// + /// DbContext. + /// Store Proc. + /// MultipleResultSetWrapper. + public static MultipleResultSetWrapper MultipleResults(this DbContext db, SqlCommand command) + { + return new MultipleResultSetWrapper(db, command); + } + + /// + /// MultipleResultSetWrapper. + /// + public class MultipleResultSetWrapper + { + private readonly DbContext dbContext; + private readonly SqlCommand command; + private List> resultSets; + + /// + /// Initializes a new instance of the class. + /// + /// DbContext. + /// Store Proc. + public MultipleResultSetWrapper(DbContext dbContext, SqlCommand storedProcedure) + { + this.dbContext = dbContext; + command = storedProcedure; + resultSets = new List>(); + } + + /// + /// With result. + /// + /// Type. + /// MultipleResultSetWrapper. + public MultipleResultSetWrapper With() + { + resultSets.Add(reader => MapToList(reader)); + + return this; + } + + /// + /// Execute. + /// + /// IEnumerable list. + public List Execute() + { + var results = new List(); + + var connection = dbContext.Database.GetDbConnection(); + + if (connection.State != ConnectionState.Open) + { + connection.Open(); + } + + command.Connection = (SqlConnection)connection; + using (var reader = command.ExecuteReader()) + { + foreach (var resultSet in resultSets) + { + results.Add(resultSet(reader)); + reader.NextResult(); + } + } + + return results; + } + + /// + /// Retrieves the column values from the stored procedure and maps them to 's properties. + /// + /// Type. + /// DbDataReader. + /// Array of type. + private static List MapToList(DbDataReader dr) + { + var objList = new List(); + var props = typeof(T).GetRuntimeProperties().ToList(); + + var colMapping = dr.GetColumnSchema() + .Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower())) + .ToDictionary(key => key.ColumnName.ToLower()); + + if (dr.HasRows) + { + while (dr.Read()) + { + T obj = Activator.CreateInstance(); + foreach (var prop in props) + { + if (colMapping.ContainsKey(prop.Name.ToLower())) + { + var column = colMapping[prop.Name.ToLower()]; + + if (column?.ColumnOrdinal != null) + { + var val = dr.GetValue(column.ColumnOrdinal.Value); + prop.SetValue(obj, val == DBNull.Value ? null : val); + } + } + } + + objList.Add(obj); + } + } + + return objList; + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationTemplateRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationTemplateRepository.cs new file mode 100644 index 000000000..5bcd5759e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationTemplateRepository.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Linq; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + + /// + /// The NotificationTemplateRepository class. + /// + public class NotificationTemplateRepository : GenericRepository, INotificationTemplateRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The Timezone offset manager. + public NotificationTemplateRepository(LearningHubDbContext context, ITimezoneOffsetManager tzOffsetManager) + : base(context, tzOffsetManager) + { + } + + /// + /// The GetById. + /// + /// The templateId. + /// The notification template. + public NotificationTemplate GetById(NotificationTemplates templateId) + { + return GetAll().SingleOrDefault(x => x.Id == (int)templateId); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ProviderRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ProviderRepository.cs new file mode 100644 index 000000000..e81581930 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ProviderRepository.cs @@ -0,0 +1,91 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using Microsoft.EntityFrameworkCore; + + /// + /// The provider repository. + /// + public class ProviderRepository : GenericRepository, IProviderRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ProviderRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + public async Task GetByIdAsync(int id) + { + return await DbContext.Provider.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id && !n.Deleted); + } + + /// + public async Task GetByIdAsync(int id, bool includeChildren) + { + if (includeChildren) + { + return await DbContext.Provider + .Where(r => !r.Deleted) + .Include(r => r.UserProvider) + .ThenInclude(pr => pr.User) + .AsNoTracking() + .FirstOrDefaultAsync(n => n.Id == id); + } + else + { + return await DbContext.Provider.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id && !n.Deleted); + } + } + + /// + /// The get by user id async. + /// + /// The user id. + /// The . + public IQueryable GetProvidersByUserIdAsync(int userId) + { + return DbContext.Set() + .Include(up => up.Provider) + .Where(up => up.UserId == userId && !up.Deleted).AsNoTracking() + .Select(up => up.Provider); + } + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + public IQueryable GetProvidersByResourceIdAsync(int resourceVersionId) + { + return DbContext.Set() + .Include(up => up.Provider) + .Where(up => up.ResourceVersionId == resourceVersionId && !up.Deleted).AsNoTracking() + .Select(up => up.Provider); + } + + /// + /// The get by node version id async. + /// + /// The node version id. + /// The . + public IQueryable GetProvidersByCatalogueIdAsync(int nodeVersionId) + { + return DbContext.Set() + .Include(up => up.Provider) + .Where(up => up.CatalogueNodeVersionId == nodeVersionId && !up.Deleted).AsNoTracking() + .Select(up => up.Provider); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs index 79b7a239b..deef84ccb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs @@ -2,37 +2,35 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories { using System; using System.Collections.Generic; - using System.ComponentModel; using System.Data; using System.Linq; using System.Threading.Tasks; - using LearningHub.Nhs.Models.Dashboard; - using LearningHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.Activity; using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; /// - public class ResourceRepository : IResourceRepository + public class ResourceRepository : GenericRepository, IResourceRepository { - private LearningHubDbContext dbContext; /// /// Initializes a new instance of the class. /// - /// . - public ResourceRepository(LearningHubDbContext dbContext) + /// The db context. + /// The Timezone offset manager. + public ResourceRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) { - this.dbContext = dbContext; } /// public async Task> GetResourcesFromIds(IEnumerable resourceIds) { - var resources = await this.dbContext.Resource + var resources = await this.DbContext.Resource .Where(r => resourceIds.Contains(r.Id)) .Where(r => !r.Deleted) .Include(r => r.ResourceReference) @@ -63,7 +61,7 @@ public async Task> GetResourcesFromIds(IEnumerable re public async Task> GetResourceReferencesByOriginalResourceReferenceIds( IEnumerable originalResourceReferenceIds) { - return await this.dbContext.ResourceReference + return await this.DbContext.ResourceReference .Where(rr => originalResourceReferenceIds.Contains(rr.OriginalResourceReferenceId)) .Where(rr => !rr.Deleted) .Where(rr => (int)rr.NodePath.Node.NodeTypeEnum != 4) @@ -84,17 +82,17 @@ public async Task> GetAchievedCertificatedResourceIds(int currentUserI var param0 = new SqlParameter("@userId", SqlDbType.Int) { Value = currentUserId }; var param4 = new SqlParameter("@TotalRecords", SqlDbType.Int) { Direction = ParameterDirection.Output }; - var result = this.dbContext.DashboardResourceDto.FromSqlRaw("resources.GetAchievedCertificatedResourcesWithOptionalPagination @userId = @userId, @TotalRecords = @TotalRecords output", param0, param4).ToList(); + var result = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetAchievedCertificatedResourcesWithOptionalPagination @userId = @userId, @TotalRecords = @TotalRecords output", param0, param4).ToList(); List achievedCertificatedResourceIds = result.Select(drd => drd.ResourceId).Distinct().ToList(); return achievedCertificatedResourceIds; } - /// - /// - /// - /// . - /// A representing the result of the asynchronous operation. + // + // + // + // . + // A representing the result of the asynchronous operation. public async Task> GetResourceActivityPerResourceMajorVersion( IEnumerable? resourceIds, IEnumerable? userIds) { @@ -109,7 +107,7 @@ public async Task> GetResourceActivityPerResour var resourceIdsParameter = new SqlParameter("@p0", resourceIdsParam ?? (object)DBNull.Value); var userIdsParameter = new SqlParameter("@p1", userIdsParam ?? (object)DBNull.Value); - List resourceActivityDTOs = await dbContext.ResourceActivityDTO + List resourceActivityDTOs = await this.DbContext.ResourceActivityDTO .FromSqlRaw( "[activity].[GetResourceActivityPerResourceMajorVersion] @p0, @p1", resourceIdsParameter, @@ -119,5 +117,75 @@ public async Task> GetResourceActivityPerResour return resourceActivityDTOs; } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await this.DbContext.Resource.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + + /// + /// Returns true if the user has any resources published. + /// + /// The user id. + /// If the user has any resources published. + public async Task UserHasPublishedResourcesAsync(int userId) + { + return await this.DbContext.Resource.AsNoTracking() + .Where(r => r.CreateUserId == userId && !r.Deleted) + .AnyAsync(); + } + + + /// + /// The create resource async. + /// + /// The resource type. + /// The title. + /// The description. + /// The user id. + /// The . + public async Task CreateResourceAsync(ResourceTypeEnum resourceType, string title, string description, int userId) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = (int)resourceType }; + var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = title }; + var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = description ?? string.Empty }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; + var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = this.TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + var param5 = new SqlParameter("@p5", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + await this.DbContext.Database.ExecuteSqlRawAsync("resources.ResourceCreate @p0, @p1, @p2, @p3, @p4, @p5 output", param0, param1, param2, param3, param4, param5); + + int resourceVersionId = (int)param5.Value; + + return resourceVersionId; + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + public async Task GetByResourceVersionIdAsync(int resourceVersionId) + { + return await this.DbContext.ResourceVersion.AsNoTracking() + ////.Include(rv => rv.Resource) + .Where(rv => rv.Id == resourceVersionId && !rv.Resource.Deleted) + .Select(rv => rv.Resource) + .FirstOrDefaultAsync(); + } + + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionFileRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionFileRepository.cs new file mode 100644 index 000000000..996cba078 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionFileRepository.cs @@ -0,0 +1,48 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.EntityFrameworkCore; + + /// + /// The article resource version file repository. + /// + public class ArticleResourceVersionFileRepository : GenericRepository, IArticleResourceVersionFileRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ArticleResourceVersionFileRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.ArticleResourceVersionFile.AsNoTracking() + .FirstOrDefaultAsync(arvf => arvf.Id == id); + } + + /// + /// The get by resourceVersionId and fileId async. + /// + /// The resourceVersionId. + /// The fileId. + /// The . + public async Task GetByResourceVersionAndFileAsync(int resourceVersionId, int fileId) + { + return await DbContext.ArticleResourceVersionFile.AsNoTracking() + .FirstOrDefaultAsync(arvf => arvf.ArticleResourceVersion.ResourceVersionId == resourceVersionId && arvf.FileId == fileId); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionRepository.cs new file mode 100644 index 000000000..71281fa17 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ArticleResourceVersionRepository.cs @@ -0,0 +1,69 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.EntityFrameworkCore; + + /// + /// The article resource version repository. + /// + public class ArticleResourceVersionRepository : GenericRepository, IArticleResourceVersionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ArticleResourceVersionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.ArticleResourceVersion.AsNoTracking() + .Include(rv => rv.ResourceVersion) + .ThenInclude(rv => rv.Resource).AsNoTracking() + .Include(rv => rv.ArticleResourceVersionFile) + .ThenInclude(rv => rv.File) + .ThenInclude(rv => rv.FileType) + .FirstOrDefaultAsync(r => r.ResourceVersionId == id && !r.ResourceVersion.Deleted); + } + + /// + /// The get by resource version id async. + /// + /// The resource versionid. + /// Allows deleted items to be returned. + /// The . + public async Task GetByResourceVersionIdAsync(int resourceVersionid, bool includeDeleted = false) + { + return await DbContext.ArticleResourceVersion + .Include(arv => arv.ArticleResourceVersionFile) + .ThenInclude(arvf => arvf.File) + .ThenInclude(rv => rv.FileType).AsNoTracking() + .AsNoTracking().FirstOrDefaultAsync(r => r.ResourceVersionId == resourceVersionid && (includeDeleted || !r.Deleted)); + } + + /// + /// The get by resource version id async. + /// + /// The id. + /// The . + public async Task GetByResourceVersionIdAsync(int id) + { + return await DbContext.ArticleResourceVersion.AsNoTracking() + .Include(rv => rv.ResourceVersion) + .ThenInclude(rv => rv.Resource).AsNoTracking() + .FirstOrDefaultAsync(r => r.ResourceVersionId == id && !r.ResourceVersion.Deleted); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AssessmentResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AssessmentResourceVersionRepository.cs new file mode 100644 index 000000000..1ccbaf036 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AssessmentResourceVersionRepository.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.EntityFrameworkCore; + + /// + /// The assessment resource version repository. + /// + public class AssessmentResourceVersionRepository : GenericRepository, IAssessmentResourceVersionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public AssessmentResourceVersionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by Resource Version Id async. + /// + /// The Resource Version Id. + /// The . + public async Task GetByResourceVersionIdAsync(int resourceVersionId) + { + return await GetAll().FirstOrDefaultAsync(arv => arv.ResourceVersionId == resourceVersionId); + } + + /// + /// Get the assessment content by Block Collection Id async. + /// + /// The Block Collection Id. + /// The . + public async Task GetByAssessmentContentBlockCollectionIdAsync(int assessmentContentId) + { + return await GetAll().FirstOrDefaultAsync(arv => arv.AssessmentContentId == assessmentContentId); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AudioResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AudioResourceVersionRepository.cs new file mode 100644 index 000000000..f9519961d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/AudioResourceVersionRepository.cs @@ -0,0 +1,52 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.EntityFrameworkCore; + + /// + /// The audio resource version repository. + /// + public class AudioResourceVersionRepository : GenericRepository, IAudioResourceVersionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public AudioResourceVersionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.AudioResourceVersion.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + } + + /// + /// The get by resource version id async. + /// + /// The resource versionid. + /// Allows deleted items to be returned. + /// The . + public async Task GetByResourceVersionIdAsync(int resourceVersionid, bool includeDeleted = false) + { + return await DbContext.AudioResourceVersion + .Include(irv => irv.File) + .Include(irv => irv.TranscriptFile) + .Include(irv => irv.ResourceAzureMediaAsset) + .Include(irv => irv.File).ThenInclude(f => f.FileType).AsNoTracking() + .Include(irv => irv.TranscriptFile).ThenInclude(f => f.FileType).AsNoTracking() + .AsNoTracking().FirstOrDefaultAsync(r => r.ResourceVersionId == resourceVersionid && (includeDeleted || !r.Deleted)); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/BlockCollectionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/BlockCollectionRepository.cs new file mode 100644 index 000000000..35b7432f1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/BlockCollectionRepository.cs @@ -0,0 +1,443 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Resource.Blocks; + using LearningHub.Nhs.OpenApi.Repositories.EntityFramework; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The block collection repository. + /// + public class BlockCollectionRepository : GenericRepository, IBlockCollectionRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public BlockCollectionRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The create async. + /// + /// The user id. + /// The block collection. + /// The . + public override async Task CreateAsync(int userId, BlockCollection blockCollection) + { + SetAuditFieldsOnChildren(userId, blockCollection, isCreate: true); + + return await base.CreateAsync(userId, blockCollection); + } + + /// + /// Delete the Block Collection. + /// + /// The User Id. + /// The Block Collection Id. + /// The . + public async Task DeleteBlockCollection(int userId, int blockCollectionId) + { + BlockCollection blockCollection = DbContext.BlockCollection + .Include(b => b.Blocks) + .ThenInclude(b => b.QuestionBlock) + .ThenInclude(b => b.Answers) + .Include(bc => bc.Blocks) + .ThenInclude(b => b.ImageCarouselBlock) + .FirstOrDefault(bc => bc.Id == blockCollectionId); + + if (blockCollection == null) + { + return; + } + + var questionBlockCollection = blockCollection.Blocks + .Where(b => b.QuestionBlock != null) + .SelectMany(b => + { + var qb = b.QuestionBlock; + return qb.Answers + .Where(t => t.BlockCollectionId.HasValue) + .Select(a => a.BlockCollectionId.Value) + .Concat(new[] { qb.QuestionBlockCollectionId, qb.FeedbackBlockCollectionId }); + }); + + var imageBlockCollection = blockCollection.Blocks + .Where(b => b.ImageCarouselBlock != null) + .Select(b => b.ImageCarouselBlock.ImageBlockCollectionId); + + var collectionIds = new[] { blockCollectionId }.Concat(imageBlockCollection.Concat(questionBlockCollection)); + + foreach (var id in collectionIds) + { + await DbContext.Database.ExecuteSqlRawAsync("resources.BlockCollectionDelete @p0", new SqlParameter("@p0", SqlDbType.Int) { Value = id }); + } + } + + /// + /// Gets the Block Collection (including child Blocks, TextBlocks, WholeSlideImageBlocks and Files). + /// + /// The Block Collection Id. + /// The . + public async Task GetBlockCollection(int? blockCollectionId) + { + if (!blockCollectionId.HasValue) + { + return null; + } + + var command = new SqlCommand + { + CommandText = "[resources].[BlockCollectionGet]", + CommandType = CommandType.StoredProcedure, + Parameters = { new SqlParameter("@BlockCollectionId", SqlDbType.Int) { Value = blockCollectionId } }, + }; + + var results = DbContext.MultipleResults(command) + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With() + .With
You've requested the maximum number of password resets (@ViewBag.Limit). Please wait @period minutes before trying again or contact the support team.
@DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss")
+ You have been logged out. Please log in using your username and new password to explore the Learning Hub. +
- Forgotten username or password + Forgotten username or password
Create new account diff --git a/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml b/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml index 14256a335..594c62f7c 100644 --- a/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml +++ b/LearningHub.Nhs.WebUI/Views/Shared/_NavPartial.cshtml @@ -49,7 +49,7 @@ @if (User.Identity.IsAuthenticated) { - + Menu diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index b459b9b6b..e67ceaa8b 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -37,6 +37,8 @@ "KeepUserSessionAliveIntervalMins": 15, "SecurityQuestionsToAsk": 2, "Restricted": false, + "PasswordRequestLimitingPeriod": 1, // minutes + "PasswordRequestLimit": 2, "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" @@ -113,7 +115,7 @@ }, "EnableTempDebugging": "false", "LimitScormToAdmin": "false" - + }, "LearningHubAuthServiceConfig": { "Authority": "", @@ -158,5 +160,5 @@ "FeatureManagement": { "ContributeAudioVideoResource": true, "DisplayAudioVideoResource": true - } + } } diff --git a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js b/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js deleted file mode 100644 index 31cd8f56e..000000000 --- a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js +++ /dev/null @@ -1,106 +0,0 @@ -var WebUI = WebUI || {}; -WebUI.PagingRequest = { Page: 1, SortColumn: '', SortDirection: '', Filter: [] }; - -WebUI.pagingSetup = function (sortColumn, sortDirection, filter) { - - WebUI.PagingRequest.SortColumn = sortColumn; - WebUI.PagingRequest.SortDirection = sortDirection; - if (!filter) { filter = []; } - WebUI.PagingRequest.Filter = filter; - - // Add Click events to column headers - $('table.lh-datatable th.orderable').attr('onClick', 'WebUI.changeTableOrder(this);'); - - // Add asc & desc class - $('table.lh-datatable th[data-column="' + sortColumn + '"].orderable').addClass(sortDirection === 'D' ? 'desc' : 'asc'); - - // Add filter row - var columns = $('table.lh-datatable.filtered thead tr th'); - if (columns.length > 0) { - row = document.createElement('tr'); - $(row).addClass('filter-row'); - - for (var i = 0; i < columns.length; i++) { - var columnName = $(columns[i]).attr('data-column'); - //var filterColumn = filter.find(x => x.Column === columnName); fails in IE11 - var filterColumn = filter.filter(function (x) { return x.Column === columnName; })[0]; - var filterValue = ''; - if (filterColumn) { filterValue = filterColumn.Value; } - var inputType = 'text'; - if ($(columns[i]).attr('data-type')) { inputType = $(columns[i]).attr('data-type'); }; - - if (columnName) { - $(row).append(''); - } else { - $(row).append(''); - } - //console.log('Add cell :' + i); - } - $('table.lh-datatable tbody').prepend(row); - - // Add click event to filter buttons - $('table.lh-datatable tr.filter-row td button').attr('onClick', 'WebUI.filterTable();'); - $('table.lh-datatable tr.filter-row td input').keypress(function (e) { - if (e.which === 13) { - WebUI.filterTable(); - return false; - } - }); - $('table.lh-datatable tr.filter-row td input')[0].focus(); - } -}; - -WebUI.requestPage = function (page) { - WebUI.PagingRequest.Page = page; - WebUI.retrievePage(); -}; - -WebUI.changeTableOrder = function (headerLink) { - var sortDirection = 'A'; - var sortColumn = $(headerLink).attr('data-column'); - var currentSortDirection = $(headerLink).hasClass('asc'); - - if (currentSortDirection) { sortDirection = 'D'; } - - WebUI.PagingRequest.SortDirection = sortDirection; - WebUI.PagingRequest.SortColumn = sortColumn; - - WebUI.retrievePage(); -}; - -WebUI.filterTable = function () { - WebUI.PagingRequest.Filter = []; - $('table.lh-datatable.filtered tbody tr.filter-row td input').each(function (index) { - var filterColumn = $(this).attr('data-column'); - var filterValue = $(this).val(); - if ($(this).val() !== '') { - WebUI.PagingRequest.Filter.push({ Column: filterColumn, Value: filterValue }); - } - }); - WebUI.retrievePage(); -}; - -WebUI.clearFilters = function () { - WebUI.PagingRequest.Page = 1; - WebUI.PagingRequest.Filter = []; - WebUI.retrievePage(); - return false; -}; - -WebUI.retrievePage = function () { - var url = window.location.pathname; - - var form = $(document.createElement('form')); - $(form).attr("action", url); - $(form).attr("method", "POST"); - - var pagingRequestString = JSON.stringify(WebUI.PagingRequest); - var input = $("") - .attr("type", "hidden") - .attr("name", "pagingRequestModel") - .val(pagingRequestString); - - $(form).append($(input)); - form.appendTo(document.body); - $(form).submit(); -}; \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs new file mode 100644 index 000000000..12d6da133 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureBlobSettings. + /// + public class AzureBlobSettings + { + /// + /// Gets or sets the connectionString. + /// + public string ConnectionString { get; set; } = null!; + + /// + /// Gets or sets the catalogue collection id. + /// + public string UploadContainer { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs new file mode 100644 index 000000000..9b3ad76d1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureConfig. + /// + public class AzureConfig + { + /// + /// Gets or sets the azure blob settings. + /// + public AzureBlobSettings AzureBlobSettings { get; set; } = null!; + /// + /// Gets or sets the azure storage queue. + /// + public string AzureStorageQueueConnectionString { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs index 3e97bbb53..ece9a6012 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs @@ -14,5 +14,10 @@ public class FindwiseCollectionIdSettings /// Gets or sets the catalogue collection id. ///
Menu diff --git a/LearningHub.Nhs.WebUI/appsettings.json b/LearningHub.Nhs.WebUI/appsettings.json index b459b9b6b..e67ceaa8b 100644 --- a/LearningHub.Nhs.WebUI/appsettings.json +++ b/LearningHub.Nhs.WebUI/appsettings.json @@ -37,6 +37,8 @@ "KeepUserSessionAliveIntervalMins": 15, "SecurityQuestionsToAsk": 2, "Restricted": false, + "PasswordRequestLimitingPeriod": 1, // minutes + "PasswordRequestLimit": 2, "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" @@ -113,7 +115,7 @@ }, "EnableTempDebugging": "false", "LimitScormToAdmin": "false" - + }, "LearningHubAuthServiceConfig": { "Authority": "", @@ -158,5 +160,5 @@ "FeatureManagement": { "ContributeAudioVideoResource": true, "DisplayAudioVideoResource": true - } + } } diff --git a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js b/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js deleted file mode 100644 index 31cd8f56e..000000000 --- a/LearningHub.Nhs.WebUI/wwwroot/js/pagingcontrol.js +++ /dev/null @@ -1,106 +0,0 @@ -var WebUI = WebUI || {}; -WebUI.PagingRequest = { Page: 1, SortColumn: '', SortDirection: '', Filter: [] }; - -WebUI.pagingSetup = function (sortColumn, sortDirection, filter) { - - WebUI.PagingRequest.SortColumn = sortColumn; - WebUI.PagingRequest.SortDirection = sortDirection; - if (!filter) { filter = []; } - WebUI.PagingRequest.Filter = filter; - - // Add Click events to column headers - $('table.lh-datatable th.orderable').attr('onClick', 'WebUI.changeTableOrder(this);'); - - // Add asc & desc class - $('table.lh-datatable th[data-column="' + sortColumn + '"].orderable').addClass(sortDirection === 'D' ? 'desc' : 'asc'); - - // Add filter row - var columns = $('table.lh-datatable.filtered thead tr th'); - if (columns.length > 0) { - row = document.createElement('tr'); - $(row).addClass('filter-row'); - - for (var i = 0; i < columns.length; i++) { - var columnName = $(columns[i]).attr('data-column'); - //var filterColumn = filter.find(x => x.Column === columnName); fails in IE11 - var filterColumn = filter.filter(function (x) { return x.Column === columnName; })[0]; - var filterValue = ''; - if (filterColumn) { filterValue = filterColumn.Value; } - var inputType = 'text'; - if ($(columns[i]).attr('data-type')) { inputType = $(columns[i]).attr('data-type'); }; - - if (columnName) { - $(row).append(''); - } else { - $(row).append(''); - } - //console.log('Add cell :' + i); - } - $('table.lh-datatable tbody').prepend(row); - - // Add click event to filter buttons - $('table.lh-datatable tr.filter-row td button').attr('onClick', 'WebUI.filterTable();'); - $('table.lh-datatable tr.filter-row td input').keypress(function (e) { - if (e.which === 13) { - WebUI.filterTable(); - return false; - } - }); - $('table.lh-datatable tr.filter-row td input')[0].focus(); - } -}; - -WebUI.requestPage = function (page) { - WebUI.PagingRequest.Page = page; - WebUI.retrievePage(); -}; - -WebUI.changeTableOrder = function (headerLink) { - var sortDirection = 'A'; - var sortColumn = $(headerLink).attr('data-column'); - var currentSortDirection = $(headerLink).hasClass('asc'); - - if (currentSortDirection) { sortDirection = 'D'; } - - WebUI.PagingRequest.SortDirection = sortDirection; - WebUI.PagingRequest.SortColumn = sortColumn; - - WebUI.retrievePage(); -}; - -WebUI.filterTable = function () { - WebUI.PagingRequest.Filter = []; - $('table.lh-datatable.filtered tbody tr.filter-row td input').each(function (index) { - var filterColumn = $(this).attr('data-column'); - var filterValue = $(this).val(); - if ($(this).val() !== '') { - WebUI.PagingRequest.Filter.push({ Column: filterColumn, Value: filterValue }); - } - }); - WebUI.retrievePage(); -}; - -WebUI.clearFilters = function () { - WebUI.PagingRequest.Page = 1; - WebUI.PagingRequest.Filter = []; - WebUI.retrievePage(); - return false; -}; - -WebUI.retrievePage = function () { - var url = window.location.pathname; - - var form = $(document.createElement('form')); - $(form).attr("action", url); - $(form).attr("method", "POST"); - - var pagingRequestString = JSON.stringify(WebUI.PagingRequest); - var input = $("") - .attr("type", "hidden") - .attr("name", "pagingRequestModel") - .val(pagingRequestString); - - $(form).append($(input)); - form.appendTo(document.body); - $(form).submit(); -}; \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs new file mode 100644 index 000000000..12d6da133 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureBlobSettings.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureBlobSettings. + /// + public class AzureBlobSettings + { + /// + /// Gets or sets the connectionString. + /// + public string ConnectionString { get; set; } = null!; + + /// + /// Gets or sets the catalogue collection id. + /// + public string UploadContainer { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs new file mode 100644 index 000000000..9b3ad76d1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/AzureConfig.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// The AzureConfig. + /// + public class AzureConfig + { + /// + /// Gets or sets the azure blob settings. + /// + public AzureBlobSettings AzureBlobSettings { get; set; } = null!; + /// + /// Gets or sets the azure storage queue. + /// + public string AzureStorageQueueConnectionString { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs index 3e97bbb53..ece9a6012 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/FindwiseCollectionIdSettings.cs @@ -14,5 +14,10 @@ public class FindwiseCollectionIdSettings /// Gets or sets the catalogue collection id. ///