diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs index 6ccd4f37c..8030cdf7d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/LearningHubConfig.cs @@ -55,5 +55,75 @@ public class LearningHubConfig /// public string ResourcePublishQueueRouteName { get; set; } = null!; + /// + /// Gets or sets . + /// + public NotificationSetting Notifications { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string MyContributionsUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string MyLearningUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string MyBookmarksUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string SearchUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string AdminUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string ForumsUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string HelpUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string MyRecordsUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string NotificationsUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string RegisterUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string SignOutUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string MyAccountUrl { get; set; } = null!; + + /// + /// Gets or sets . + /// + public string BrowseCataloguesUrl { get; set; } = null!; + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs new file mode 100644 index 000000000..7b9b7b5ba --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/Configuration/NotificationSetting.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LearningHub.Nhs.OpenApi.Models.Configuration +{ + /// + /// Gets or sets NotificationSetting. + /// + public class NotificationSetting + { + /// + /// Gets or sets the PublishResourceTimeToProcessInSec. + /// + public int PublishResourceTimeToProcessInSec { get; set; } + + /// + /// Gets or sets the ResourcePublishedTitle. + /// + public string ResourcePublishedTitle { get; set; } = null!; + + /// + /// Gets or sets the ResourcePublished. + /// + public string ResourcePublished { get; set; } = null!; + + /// + /// Gets or sets the ResourcePublishFailedTitle. + /// + public string ResourcePublishFailedTitle { get; set; } = null!; + + /// + /// Gets or sets the ResourcePublishFailed. + /// + public string ResourcePublishFailed { get; set; } = null!; + + /// + /// Gets or sets the ResourcePublishFailedWithReason. + /// + public string ResourcePublishFailedWithReason { get; set; } = null!; + + /// + /// Gets or sets the ResourceAccessTitle. + /// + public string ResourceAccessTitle { get; set; } = null!; + + /// + /// Gets or sets the ResourceReadonlyAccess. + /// + public string ResourceReadonlyAccess { get; set; } = null!; + + /// + /// Gets or sets the ResourceContributeAccess. + /// + public string ResourceContributeAccess { get; set; } = null!; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs new file mode 100644 index 000000000..81f599c3f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/ViewModels/NavigationModel.cs @@ -0,0 +1,78 @@ +namespace LearningHub.Nhs.OpenApi.Models.ViewModels +{ + /// + /// Defines the . + /// + public class NavigationModel + { + /// + /// Gets or sets the NotificationCount. + /// + public int NotificationCount { get; set; } + + /// + /// Gets or sets a value indicating whether ShowAdmin. + /// + public bool ShowAdmin { get; set; } + + /// + /// Gets or sets a value indicating whether ShowForums. + /// + public bool ShowForums { get; set; } + + /// + /// Gets or sets a value indicating whether ShowHelp. + /// + public bool ShowHelp { get; set; } + + /// + /// Gets or sets a value indicating whether ShowMyContributions. + /// + public bool ShowMyContributions { get; set; } + + /// + /// Gets or sets a value indicating whether ShowMyLearning. + /// + public bool ShowMyLearning { get; set; } + + /// + /// Gets or sets a value indicating whether ShowMyBookmarks. + /// + public bool ShowMyBookmarks { get; set; } + + /// + /// Gets or sets a value indicating whether ShowSearch. + /// + public bool ShowSearch { get; set; } + + /// + /// Gets or sets a value indicating whether ShowMyRecords. + /// + public bool ShowMyRecords { get; set; } + + /// + /// Gets or sets a value indicating whether ShowNotifications. + /// + public bool ShowNotifications { get; set; } + + /// + /// Gets or sets a value indicating whether ShowRegister. + /// + public bool ShowRegister { get; set; } + + /// + /// Gets or sets a value indicating whether ShowSignOut. + /// + public bool ShowSignOut { get; set; } + + /// + /// Gets or sets a value indicating whether to show my account. + /// + public bool ShowMyAccount { get; set; } + + /// + /// Gets or sets a value indicating whether to show Browse Catalogues. + /// + public bool ShowBrowseCatalogues { get; set; } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationRepository.cs new file mode 100644 index 000000000..a70a7f2e1 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/INotificationRepository.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + + /// + /// The NotificationRepository interface. + /// + public interface INotificationRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get all full. + /// + /// The . + IQueryable GetAllFull(); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs index 1b9d45411..f340ce670 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IResourceRepository.cs @@ -72,5 +72,12 @@ Task> GetResourceReferencesByOriginalResourceRefe /// The . Task GetByResourceVersionIdAsync(int resourceVersionId); + /// + /// Returns a bool to indicate if the resourceVersionId corresponds to a current version of a resource. + /// + /// The resourceVersionId. + /// The . + Task IsCurrentVersionAsync(int resourceVersionId); + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserNotificationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserNotificationRepository.cs new file mode 100644 index 000000000..b96401ba9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/IUserNotificationRepository.cs @@ -0,0 +1,45 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + + /// + /// The UserNotificationRepository interface. + /// + public interface IUserNotificationRepository : IGenericRepository + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by notification and user id async. + /// + /// The user id. + /// The notification id. + /// The . + Task GetByNotificationAndUserIdAsync(int userId, int notificationId); + + /// + /// The get user unread notification count async. + /// + /// The userid. + /// The . + Task GetUserUnreadNotificationCountAsync(int userid); + + /// + /// The get all non dismissed. + /// + /// The user id. + /// Notification priority type. + /// The sort column. + /// The sort direction. + /// The . + IQueryable GetAllNonDismissed(int userId, NotificationPriorityEnum priorityType, string sortColumn = "", string sortDirection = ""); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceSyncRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceSyncRepository.cs new file mode 100644 index 000000000..98b1ea9e2 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceSyncRepository.cs @@ -0,0 +1,44 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The IResourceSyncRepository. + /// + public interface IResourceSyncRepository + { + /// + /// The GetSyncListForUser. + /// + /// The userid. + /// If the resource property should be populated. + /// The sync list for the user. + IQueryable GetSyncListForUser(int userId, bool includeResources); + + /// + /// The AddToSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + Task AddToSyncListAsync(int userId, List resourceIds); + + /// + /// The RemoveFromSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + Task RemoveFromSyncListAsync(int userId, List resourceIds); + + /// + /// The SetSyncedForUserAsync. + /// + /// The userId. + /// The task. + Task SetSyncedForUserAsync(int userId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingRepository.cs new file mode 100644 index 000000000..aad4e49b7 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingRepository.cs @@ -0,0 +1,26 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionEventRepository interface. + /// + public interface IResourceVersionRatingRepository : IGenericRepository + { + /// + /// Gets a user's previous rating for any minor version of the current major resource version. + /// + /// The resourceVersionId. + /// The userId. + /// The . + Task GetUsersPreviousRatingForSameMajorVersionAsync(int resourceVersionId, int userId); + + /// + /// Gets the total rating counts for a particular resource version PLUS all other minor versions of the same major version. + /// + /// The resourceVersionId. + /// An array of integers, which are the count for each star value, starting at 1 star and ending with 5 stars. + Task GetRatingCountsForResourceVersionAsync(int resourceVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingSummaryRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingSummaryRepository.cs new file mode 100644 index 000000000..3fb2603f9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories.Interface/Repositories/Resources/IResourceVersionRatingSummaryRepository.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + + /// + /// The ResourceVersionEventRepository interface. + /// + public interface IResourceVersionRatingSummaryRepository : 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/EntityFramework/LearningHubDbContext.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs index 559f982e5..680d27ddb 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/LearningHubDbContext.cs @@ -24,7 +24,7 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework /// /// The learning hub db context. /// - public class LearningHubDbContext : DbContext + public partial class LearningHubDbContext : DbContext { /// /// The options.. @@ -41,6 +41,14 @@ public LearningHubDbContext(LearningHubDbContextOptions options) this.options = options; } + /// + /// Gets the Options. + /// + public LearningHubDbContextOptions Options + { + get { return this.options; } + } + /// /// Gets or sets the Address. /// @@ -177,6 +185,11 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet ResourceVersionValidationResult { get; set; } + /// + /// Gets or sets the resource version validation rule result. + /// + public virtual DbSet ResourceVersionValidationRuleResult { get; set; } + /// /// Gets or sets the resource version event.. /// @@ -466,6 +479,8 @@ public LearningHubDbContext(LearningHubDbContextOptions options) /// public virtual DbSet CatalogueNodeVersion { get; set; } + public virtual DbSet CatalogueNodeVersionProvider { get; set; } + /// /// Gets or sets the catalogue node version keyword.. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs index 8129bc9c8..964c7eac7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/EntityFramework/ServiceMappings.cs @@ -7,8 +7,10 @@ namespace LearningHub.Nhs.OpenApi.Repositories.EntityFramework using LearningHub.Nhs.OpenApi.Repositories.Map.Content; using LearningHub.Nhs.OpenApi.Repositories.Map.External; using LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance; using LearningHub.Nhs.OpenApi.Repositories.Map.Messaging; using LearningHub.Nhs.OpenApi.Repositories.Map.Resources; + using LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -63,6 +65,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -77,11 +80,13 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -95,6 +100,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -103,8 +109,14 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -123,6 +135,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -135,15 +148,21 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -152,6 +171,7 @@ public static void AddLearningHubMappings(this IServiceCollection services, ICon services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Activity/AssessmentResourceActivityMatchQuestionMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Activity/AssessmentResourceActivityMatchQuestionMap.cs new file mode 100644 index 000000000..e9277cfac --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Activity/AssessmentResourceActivityMatchQuestionMap.cs @@ -0,0 +1,39 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Activity +{ + using LearningHub.Nhs.Models.Entities.Activity; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The resource activity match question map. + /// + public class AssessmentResourceActivityMatchQuestionMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("AssessmentResourceActivityMatchQuestion", "activity"); + + modelBuilder.HasOne(e => e.AssessmentResourceActivity) + .WithMany(e => e.MatchQuestions) + .HasForeignKey(d => d.AssessmentResourceActivityId) + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_AssessmentResourceActivityMatchQuestion_AssessmentResourceActivityId"); + + modelBuilder.HasOne(e => e.FirstMatchAnswer) + .WithOne() + .HasForeignKey(d => d.FirstMatchAnswerId) + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_AssessmentResourceActivityMatchQuestion_FirstMatchAnswerId"); + + modelBuilder.HasOne(e => e.SecondMatchAnswer) + .WithOne() + .HasForeignKey(d => d.SecondMatchAnswerId) + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_AssessmentResourceActivityMatchQuestion_SecondMatchAnswerId"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/EmailChangeValidationTokenMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/EmailChangeValidationTokenMap.cs new file mode 100644 index 000000000..14406a08f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/EmailChangeValidationTokenMap.cs @@ -0,0 +1,23 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map +{ + using LearningHub.Nhs.Models.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The email change validation token map. + /// + public class EmailChangeValidationTokenMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// + /// The model builder. + /// + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("EmailChangeValidationToken", "hub"); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/CatalogueNodeVersionProviderMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/CatalogueNodeVersionProviderMap.cs new file mode 100644 index 000000000..0e3dedc5a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/CatalogueNodeVersionProviderMap.cs @@ -0,0 +1,28 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The CatalogueNodeVersionProviderMap. + /// + public class CatalogueNodeVersionProviderMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The modelBuilder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("CatalogueNodeVersionProvider", "hierarchy"); + modelBuilder.HasKey(e => e.Id); + modelBuilder.Property(e => e.Id) + .HasColumnName("Id"); + modelBuilder.Property(e => e.CatalogueNodeVersionId) + .HasColumnName("CatalogueNodeVersionId").IsRequired(); + modelBuilder.Property(e => e.ProviderId).HasColumnName("ProviderId"); + modelBuilder.Property(e => e.RemovalDate).HasColumnName("RemovalDate").IsRequired(false); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/FolderNodeVersionMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/FolderNodeVersionMap.cs new file mode 100644 index 000000000..6b2beab84 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/FolderNodeVersionMap.cs @@ -0,0 +1,35 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The FolderNodeVersionMap. + /// + public class FolderNodeVersionMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The modelBuilder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("FolderNodeVersion", "hierarchy"); + + modelBuilder.Property(x => x.Name) + .IsRequired() + .HasMaxLength(255); + + modelBuilder.Property(x => x.Description) + .IsRequired() + .HasMaxLength(1800); + + modelBuilder.HasOne(d => d.NodeVersion) + .WithOne(p => p.FolderNodeVersion) + .HasForeignKey(d => d.NodeVersionId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_FolderNodeVersion_NodeVersion"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs new file mode 100644 index 000000000..893fbfc25 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditDetailMap.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The hierarchy edit map. + /// + public class HierarchyEditDetailMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("HierarchyEditDetail", "hierarchy"); + + modelBuilder.HasOne(d => d.HierarchyEdit) + .WithMany(p => p.HierarchyEditDetail) + .HasForeignKey(d => d.HierarchyEditId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEditDetail_HierarchyEdit"); + + modelBuilder.Property(e => e.HierarchyEditDetailType).HasColumnName("HierarchyEditDetailTypeId") + .HasConversion(); + + modelBuilder.Property(e => e.HierarchyEditDetailOperation).HasColumnName("HierarchyEditDetailOperationId") + .HasConversion(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs new file mode 100644 index 000000000..23883290e --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/HierarchyEditMap.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The hierarchy edit map. + /// + public class HierarchyEditMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("HierarchyEdit", "hierarchy"); + + modelBuilder.Property(e => e.HierarchyEditStatus).HasColumnName("HierarchyEditStatusId") + .HasConversion(); + + modelBuilder.HasOne(d => d.CreateUser) + .WithMany(p => p.HierarchyEdit) + .HasForeignKey(d => d.CreateUserId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEdit_CreateUser"); + + modelBuilder.HasOne(d => d.RootNode) + .WithMany() + .HasForeignKey(d => d.RootNodeId) + .HasConstraintName("FK_HierarchyEdit_Node"); + + modelBuilder.HasOne(d => d.Publication) + .WithMany() + .HasForeignKey(d => d.PublicationId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HierarchyEdit_Publication"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs new file mode 100644 index 000000000..d0d98093f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Hierarchy/NodeResourceLookupMap.cs @@ -0,0 +1,33 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Hierarchy +{ + using LearningHub.Nhs.Models.Entities.Hierarchy; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The node resource lookup map. + /// + public class NodeResourceLookupMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("NodeResourceLookup", "hierarchy"); + + modelBuilder.HasOne(d => d.Node) + .WithMany() + .HasForeignKey(d => d.NodeId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_NodeResourceLookup_Node"); + + modelBuilder.HasOne(d => d.Resource) + .WithMany() + .HasForeignKey(d => d.ResourceId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_NodeResourceLookup_Resource"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs new file mode 100644 index 000000000..6da03f6fd --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Maintenance/InternalSystemMap.cs @@ -0,0 +1,32 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Maintenance +{ + using LearningHub.Nhs.Models.Entities.Maintenance; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// Defines the . + /// + public class InternalSystemMap : BaseEntityMap + { + /// + /// The InternalMap. + /// + /// The entity. + protected override void InternalMap(EntityTypeBuilder entity) + { + entity.ToTable("InternalSystem", "maintenance"); + + entity.HasIndex(e => e.Name) + .IsUnique(); + + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(50); + + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(2000); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ProviderMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ProviderMap.cs new file mode 100644 index 000000000..0f0f5af2d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/ProviderMap.cs @@ -0,0 +1,39 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map +{ + using LearningHub.Nhs.Models.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The provider map. + /// + public class ProviderMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("Provider", "hub"); + + modelBuilder.HasKey(e => e.Id); + + modelBuilder.Property(e => e.Id) + .HasColumnName("Id"); + + modelBuilder.Property(e => e.Description) + .HasColumnName("Description") + .HasMaxLength(255); + + modelBuilder.Property(e => e.Logo) + .HasColumnName("Logo") + .HasMaxLength(100); + + modelBuilder.Property(e => e.Name) + .IsRequired() + .HasColumnName("Name") + .HasMaxLength(255); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs similarity index 65% rename from OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs index 00f7f46cb..fd31afb51 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMap.cs @@ -1,20 +1,18 @@ -namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks { using LearningHub.Nhs.Models.Entities.Resource; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; /// - /// The whole slide image annotation map. + /// The image annotation map. /// public class ImageAnnotationMap : BaseEntityMap { /// /// The internal map. /// - /// - /// The model builder. - /// + /// The model builder. protected override void InternalMap(EntityTypeBuilder modelBuilder) { modelBuilder.ToTable("ImageAnnotation", "resources"); @@ -24,6 +22,12 @@ protected override void InternalMap(EntityTypeBuilder modelBuil .HasForeignKey(a => a.WholeSlideImageId) .OnDelete(DeleteBehavior.Cascade) .HasConstraintName("FK_ImageAnnotation_WholeSlideImageId"); + + modelBuilder.HasOne(a => a.Image) + .WithMany(i => i.ImageAnnotations) + .HasForeignKey(a => a.ImageId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageAnnotation_ImageId"); } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs similarity index 80% rename from OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs rename to OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs index 9db0a6db6..a1bad42e9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/WholeSlideImageAnnotationMarkMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageAnnotationMarkMap.cs @@ -1,20 +1,18 @@ -namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks { using LearningHub.Nhs.Models.Entities.Resource; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; /// - /// The whole slide image annotation map. + /// The image annotation mark map. /// public class ImageAnnotationMarkMap : BaseEntityMap { /// /// The internal map. /// - /// - /// The model builder. - /// + /// The model builder. protected override void InternalMap(EntityTypeBuilder modelBuilder) { modelBuilder.ToTable("ImageAnnotationMark", "resources"); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs new file mode 100644 index 000000000..706269026 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/Blocks/ImageCarouselBlockMap.cs @@ -0,0 +1,36 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources.Blocks +{ + using LearningHub.Nhs.Models.Entities.Resource.Blocks; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// the image carousel block map. + /// + public class ImageCarouselBlockMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("ImageCarouselBlock", "resources"); + + modelBuilder.Property(e => e.BlockId).ValueGeneratedNever(); + modelBuilder.HasAlternateKey(c => c.BlockId); + + modelBuilder.HasOne(d => d.Block) + .WithOne(p => p.ImageCarouselBlock) + .HasForeignKey(d => d.BlockId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageCarouselBlock_BlockId"); + + modelBuilder.HasOne(d => d.ImageBlockCollection) + .WithOne() + .HasForeignKey(d => d.ImageBlockCollectionId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_ImageCarouselBlock_ImageBlockCollectionId"); + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/HtmlResourceVersionMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/HtmlResourceVersionMap.cs new file mode 100644 index 000000000..b9608cbec --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/HtmlResourceVersionMap.cs @@ -0,0 +1,40 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +{ + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.OpenApi.Repositories.Map; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The html resource version map. + /// + public class HtmlResourceVersionMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.HasKey(e => e.Id) + .HasName("PK_Resources_HtmlResourceVersion"); + + modelBuilder.ToTable("HtmlResourceVersion", "resources"); + + modelBuilder.Property(e => e.ResourceVersionId).ValueGeneratedNever(); + modelBuilder.HasAlternateKey(c => c.ResourceVersionId); + + modelBuilder.HasOne(d => d.File) + .WithMany(p => p.HtmlResourceVersion) + .HasForeignKey(d => d.FileId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HtmlResource_File"); + + modelBuilder.HasOne(d => d.ResourceVersion) + .WithOne(p => p.HtmlResourceVersion) + .HasForeignKey(d => d.ResourceVersionId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_HtmlResourceVersion_ResourceVersion"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionProviderMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionProviderMap.cs new file mode 100644 index 000000000..563e9dd3b --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionProviderMap.cs @@ -0,0 +1,35 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map.Resources +{ + using LearningHub.Nhs.Models.Entities.Resource; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// The resource version provider map. + /// + public class ResourceVersionProviderMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The model builder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("ResourceVersionProvider", "resources"); + modelBuilder.Property(e => e.ProviderId).HasColumnName("ProviderId"); + modelBuilder.Property(e => e.ResourceVersionId).HasColumnName("ResourceVersionId"); + + modelBuilder.HasOne(d => d.ResourceVersion) + .WithMany(p => p.ResourceVersionProvider) + .HasForeignKey(d => d.ResourceVersionId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_resourceVersionProvider_resourceVersion"); + + modelBuilder.HasOne(d => d.Provider) + .WithMany(p => p.ResourceVersionProvider) + .HasForeignKey(d => d.ProviderId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_resourceVersionProvider_provider"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionValidationResultMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionValidationResultMap.cs index dc0bbfaba..e62ee5858 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionValidationResultMap.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/Resources/ResourceVersionValidationResultMap.cs @@ -22,12 +22,6 @@ protected override void InternalMap(EntityTypeBuilder d.ResourceVersion) - .WithMany() - .HasForeignKey(d => d.ResourceVersionId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("FK_ResourceVersionValidationResult_ResourceVersion"); - modelBuilder.Property(e => e.Details) .IsRequired() .HasMaxLength(1024); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserBookmarkMap.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserBookmarkMap.cs new file mode 100644 index 000000000..87d2a23be --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Map/UserBookmarkMap.cs @@ -0,0 +1,21 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Map +{ + using LearningHub.Nhs.Models.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + + /// + /// Defines the . + /// + public class UserBookmarkMap : BaseEntityMap + { + /// + /// The internal map. + /// + /// The modelBuilder. + protected override void InternalMap(EntityTypeBuilder modelBuilder) + { + modelBuilder.ToTable("UserBookmark", "hub"); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs index cff356d60..08a8051a2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/GenericRepository.cs @@ -22,8 +22,8 @@ public class GenericRepository : IGenericRepository /// The Timezone offset manager. public GenericRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) { - DbContext = dbContext; - TimezoneOffsetManager = tzOffsetManager; + this.DbContext = dbContext; + this.TimezoneOffsetManager = tzOffsetManager; } /// @@ -42,7 +42,7 @@ public GenericRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager /// The . public IQueryable GetAll() { - return DbContext.Set().AsNoTracking(); + return this.DbContext.Set().AsNoTracking(); } /// @@ -53,18 +53,18 @@ public IQueryable GetAll() /// The . public virtual async Task CreateAsync(int userId, TEntity entity) { - await DbContext.Set().AddAsync(entity); - SetAuditFieldsForCreate(userId, entity); + await this.DbContext.Set().AddAsync(entity); + this.SetAuditFieldsForCreate(userId, entity); try { - await DbContext.SaveChangesAsync(); + await this.DbContext.SaveChangesAsync(); } catch (Exception) { throw; } - DbContext.Entry(entity).State = EntityState.Detached; + this.DbContext.Entry(entity).State = EntityState.Detached; return entity.Id; } @@ -77,13 +77,13 @@ public virtual async Task CreateAsync(int userId, TEntity entity) /// The . public virtual async Task UpdateAsync(int userId, TEntity entity) { - DbContext.Set().Update(entity); + this.DbContext.Set().Update(entity); - SetAuditFieldsForUpdate(userId, entity); + this.SetAuditFieldsForUpdate(userId, entity); - await DbContext.SaveChangesAsync(); + await this.DbContext.SaveChangesAsync(); - DbContext.Entry(entity).State = EntityState.Detached; + this.DbContext.Entry(entity).State = EntityState.Detached; } /// @@ -93,13 +93,13 @@ public virtual async Task UpdateAsync(int userId, TEntity entity) /// The entity. public virtual void Update(int userId, TEntity entity) { - DbContext.Set().Update(entity); + this.DbContext.Set().Update(entity); - SetAuditFieldsForUpdate(userId, entity); + this.SetAuditFieldsForUpdate(userId, entity); - DbContext.SaveChanges(); + this.DbContext.SaveChanges(); - DbContext.Entry(entity).State = EntityState.Detached; + this.DbContext.Entry(entity).State = EntityState.Detached; } /// @@ -109,7 +109,7 @@ public virtual void Update(int userId, TEntity entity) /// The entity. public void SetAuditFieldsForCreate(int userId, EntityBase entity) { - var amendDate = GetAmendDate(); + var amendDate = this.GetAmendDate(); entity.Deleted = false; entity.CreateUserId = userId; @@ -128,11 +128,11 @@ public void SetAuditFieldsForCreateOrDelete(int userId, EntityBase entity, bool { if (isCreate) { - SetAuditFieldsForCreate(userId, entity); + this.SetAuditFieldsForCreate(userId, entity); } else { - SetAuditFieldsForDelete(userId, entity); + this.SetAuditFieldsForDelete(userId, entity); } } @@ -144,13 +144,13 @@ public void SetAuditFieldsForCreateOrDelete(int userId, EntityBase entity, bool 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; + entity.AmendDate = this.GetAmendDate(); + this.DbContext.Entry(entity).Property("CreateUserId").IsModified = false; + this.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; + this.DbContext.Entry(entity).Property("VersionStartTime").IsModified = false; + this.DbContext.Entry(entity).Property("VersionEndTime").IsModified = false; } } @@ -163,14 +163,14 @@ 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; + entity.AmendDate = this.GetAmendDate(); + this.DbContext.Entry(entity).Property("CreateUserId").IsModified = false; + this.DbContext.Entry(entity).Property("CreateDate").IsModified = false; } private DateTimeOffset GetAmendDate() { - var tzOffset = TimezoneOffsetManager.UserTimezoneOffset; + var tzOffset = this.TimezoneOffsetManager.UserTimezoneOffset; return tzOffset.HasValue ? new DateTimeOffset(DateTime.UtcNow.AddMinutes(tzOffset.Value).Ticks, TimeSpan.FromMinutes(tzOffset.Value)) : DateTimeOffset.Now; } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationRepository.cs new file mode 100644 index 000000000..71f9335f8 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/NotificationRepository.cs @@ -0,0 +1,52 @@ +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 notification repository. + /// + public class NotificationRepository : GenericRepository, INotificationRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public NotificationRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public Task GetByIdAsync(int id) + { + return DbContext.Notification + .Include(n => n.AmendUser) + .Include(n => n.CreateUser) + .Where(n => n.Id == id) + .AsNoTracking() + .SingleOrDefaultAsync(); + } + + /// + /// The get all full. + /// + /// The . + public IQueryable GetAllFull() + { + return DbContext.Set() + .Include(n => n.AmendUser) + .Include(n => n.CreateUser) + .AsNoTracking(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs index deef84ccb..a0004a6e2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/ResourceRepository.cs @@ -16,7 +16,6 @@ namespace LearningHub.Nhs.OpenApi.Repositories.Repositories /// public class ResourceRepository : GenericRepository, IResourceRepository { - /// /// Initializes a new instance of the class. /// @@ -187,5 +186,16 @@ public async Task GetByResourceVersionIdAsync(int resourceVersionId) } + /// + /// Returns a bool to indicate if the resourceVersionId corresponds to a current version of a resource. + /// + /// The resourceVersionId. + /// The . + public async Task IsCurrentVersionAsync(int resourceVersionId) + { + return await this.DbContext.Resource.AnyAsync(r => r.CurrentResourceVersionId == resourceVersionId && !r.Deleted); + } + + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/HtmlResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/HtmlResourceVersionRepository.cs index e12ff8f6e..3f1cffcc2 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/HtmlResourceVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/HtmlResourceVersionRepository.cs @@ -30,7 +30,7 @@ public HtmlResourceVersionRepository(LearningHubDbContext dbContext, ITimezoneOf /// The . public async Task GetByIdAsync(int id) { - return await DbContext.HtmlResourceVersion.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); + return await this.DbContext.HtmlResourceVersion.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && !r.Deleted); } /// @@ -41,7 +41,7 @@ public async Task GetByIdAsync(int id) /// The . public async Task GetByResourceVersionIdAsync(int resourceVersionid, bool includeDeleted = false) { - return await DbContext.HtmlResourceVersion + return await this.DbContext.HtmlResourceVersion .Include(gfrv => gfrv.File).ThenInclude(f => f.FileType) .AsNoTracking().FirstOrDefaultAsync(gfrv => gfrv.ResourceVersionId == resourceVersionid && (includeDeleted || !gfrv.Deleted)); } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceSyncRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceSyncRepository.cs new file mode 100644 index 000000000..b4fa27438 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceSyncRepository.cs @@ -0,0 +1,105 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System; + using System.Collections.Generic; + using System.Linq; + 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 resource sync repository. + /// + public class ResourceSyncRepository : GenericRepository, IResourceSyncRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The dbcontext. + /// The Timezone offset manager. + public ResourceSyncRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The GetSyncListForUser. + /// + /// The userid. + /// If the resource property should be populated. + /// The sync list for the user. + public IQueryable GetSyncListForUser(int userId, bool includeResources) + { + var resourceSyncs = GetAll(); + if (includeResources) + { + resourceSyncs = resourceSyncs + .Include(x => x.Resource).ThenInclude(x => x.CreateUser) + .Include(x => x.Resource).ThenInclude(x => x.Resource).ThenInclude(r => r.ResourceReference) + .Include(x => x.Resource).ThenInclude(x => x.ResourceVersionKeyword) + .Include(x => x.Resource).ThenInclude(x => x.ResourceVersionAuthor) + .Include(x => x.Resource).ThenInclude(x => x.ResourceVersionRatingSummary) + .Include(x => x.Resource).ThenInclude(x => x.Publication); + } + + return resourceSyncs.Where(x => x.UserId == userId); + } + + /// + /// The AddToSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + public async Task AddToSyncListAsync(int userId, List resourceIds) + { + foreach (var resourceId in resourceIds) + { + var resourceSync = new ResourceSync { ResourceId = resourceId, UserId = userId }; + await CreateAsync(userId, resourceSync); + } + } + + /// + /// The RemoveFromSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + public async Task RemoveFromSyncListAsync(int userId, List resourceIds) + { + var syncsToRemove = GetAll().Where(x => x.UserId == userId && resourceIds.Contains(x.ResourceId)).ToList(); + foreach (var sync in syncsToRemove) + { + SetAuditFieldsForDelete(userId, sync); + try + { + await UpdateAsync(userId, sync); + } + catch (Exception ex) + { + var a = ex; + throw a; + } + } + } + + /// + /// The SetSyncedForUserAsync. + /// + /// The userId. + /// The task. + public async Task SetSyncedForUserAsync(int userId) + { + var resourceSyncs = GetSyncListForUser(userId, false).ToList(); + foreach (var sync in resourceSyncs) + { + SetAuditFieldsForDelete(userId, sync); + await UpdateAsync(userId, sync); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingRepository.cs new file mode 100644 index 000000000..d332fcd52 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingRepository.cs @@ -0,0 +1,77 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Collections.Generic; + using System.Linq; + 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 resource version Event repository. + /// + public class ResourceVersionRatingRepository : GenericRepository, IResourceVersionRatingRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ResourceVersionRatingRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// Gets a user's previous rating for any minor version of the same major resource version. + /// + /// The resourceVersionId. + /// The userId. + /// The . + public async Task GetUsersPreviousRatingForSameMajorVersionAsync(int resourceVersionId, int userId) + { + var minorVersionIds = await GetAllResourceVersionIdsForSameMajorVersion(resourceVersionId); + + return await DbContext.ResourceVersionRating.FirstOrDefaultAsync(x => minorVersionIds.Contains(x.ResourceVersionId) && x.UserId == userId && !x.Deleted); + } + + /// + /// Gets the total rating counts across all users for a particular resource version. + /// + /// The resourceVersionId. + /// An array of integers, which are the count for each star value, starting at 1 star and ending with 5 stars. + public async Task GetRatingCountsForResourceVersionAsync(int resourceVersionId) + { + var minorVersionIds = await GetAllResourceVersionIdsForSameMajorVersion(resourceVersionId); + + var allMajorVersionRatings = DbContext.ResourceVersionRating.Where(x => minorVersionIds.Contains(x.ResourceVersionId)); + + int[] starCounts = new int[5]; + + starCounts[0] = await allMajorVersionRatings.CountAsync(x => x.Rating == 1); + starCounts[1] = await allMajorVersionRatings.CountAsync(x => x.Rating == 2); + starCounts[2] = await allMajorVersionRatings.CountAsync(x => x.Rating == 3); + starCounts[3] = await allMajorVersionRatings.CountAsync(x => x.Rating == 4); + starCounts[4] = await allMajorVersionRatings.CountAsync(x => x.Rating == 5); + + return starCounts; + } + + /// + /// Gets a list of resource version Ids for ALL minor versions of the same major resource version. + /// + /// The resource version id. + /// A list of resoruce verison ids. + private async Task> GetAllResourceVersionIdsForSameMajorVersion(int resourceVersionId) + { + var majorVersionInfo = await DbContext.ResourceVersion.Where(x => x.Id == resourceVersionId).Select(x => new { x.ResourceId, x.MajorVersion }).FirstOrDefaultAsync(); + + var minorVersionIds = await DbContext.ResourceVersion + .Where(x => x.ResourceId == majorVersionInfo.ResourceId && x.MajorVersion == majorVersionInfo.MajorVersion).Select(x => x.Id).ToListAsync(); + + return minorVersionIds; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingSummaryRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingSummaryRepository.cs new file mode 100644 index 000000000..34bf3358d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRatingSummaryRepository.cs @@ -0,0 +1,37 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories.Resources +{ + using System.Linq; + 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 resource version rating summary repository. + /// + public class ResourceVersionRatingSummaryRepository : GenericRepository, IResourceVersionRatingSummaryRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public ResourceVersionRatingSummaryRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by resource version id async. + /// + /// The resource version id. + /// The . + public Task GetByResourceVersionIdAsync(int resourceVersionId) + { + return DbContext.ResourceVersionRatingSummary.Where(r => r.ResourceVersionId == resourceVersionId && !r.Deleted).AsNoTracking() + .SingleOrDefaultAsync(); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs index 6ead541f8..be139ffc8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs @@ -38,7 +38,7 @@ public ResourceVersionRepository(LearningHubDbContext dbContext, ITimezoneOffset /// The . public IQueryable GetAllAdminSearch(int userId) { - return DbContext.Set() + return this.DbContext.Set() .Include(r => r.Resource) .ThenInclude(r => r.ResourceReference) .Include(n => n.CreateUser) @@ -59,7 +59,7 @@ public IQueryable GetAllAdminSearch(int userId) /// The resource version list. public async Task> GetResourceVersionsForSearchSubmission(List resourceVersionIds) { - return await DbContext.ResourceVersion + return await this.DbContext.ResourceVersion .Include(x => x.Resource).ThenInclude(r => r.ResourceReference) .Include(x => x.ResourceVersionKeyword) .Include(x => x.ResourceVersionAuthor) @@ -81,7 +81,7 @@ public async Task> GetResourceVersionsForSearchSubmission( /// The . public async Task GetBasicByIdAsync(int id) { - return await DbContext.ResourceVersion + return await this.DbContext.ResourceVersion .Include(rv => rv.Resource) .ThenInclude(r => r.CurrentResourceVersion) .Include(rv => rv.Resource) @@ -105,7 +105,7 @@ public async Task GetByIdAsync(int id, bool includeEvents) ResourceVersion retVal; if (includeEvents) { - retVal = await DbContext.ResourceVersion.AsNoTracking() + retVal = await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource) .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -121,7 +121,7 @@ public async Task GetByIdAsync(int id, bool includeEvents) } else { - retVal = await DbContext.ResourceVersion.AsNoTracking() + retVal = await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource) .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -145,7 +145,7 @@ public async Task GetByIdAsync(int id, bool includeEvents) /// The . public async Task GetCurrentForResourceAsync(int resourceId) { - return await DbContext.ResourceVersion.AsNoTracking() + return await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource) .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -168,7 +168,7 @@ public async Task GetCurrentForResourceAsync(int resourceId) /// The . public async Task GetCurrentResourceDetailsAsync(int resourceId) { - return await DbContext.ResourceVersion.AsNoTracking() + return await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource) .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -192,7 +192,7 @@ public async Task GetCurrentResourceDetailsAsync(int resourceId /// The . public async Task GetByResourceVersionByIdAsync(int resourceVersionId) { - return await DbContext.ResourceVersion.OrderByDescending(r => r.Id).FirstOrDefaultAsync(x => x.Id == resourceVersionId); + return await this.DbContext.ResourceVersion.OrderByDescending(r => r.Id).FirstOrDefaultAsync(x => x.Id == resourceVersionId); } /// @@ -202,7 +202,7 @@ public async Task GetByResourceVersionByIdAsync(int resourceVer /// The . public async Task DoesDevIdExistsAync(string devId) { - return await DbContext.ResourceVersion.OrderByDescending(r => r.Id).FirstOrDefaultAsync(x => x.DevId == devId); + return await this.DbContext.ResourceVersion.OrderByDescending(r => r.Id).FirstOrDefaultAsync(x => x.DevId == devId); } /// @@ -212,7 +212,7 @@ public async Task DoesDevIdExistsAync(string devId) /// The . public async Task GetCurrentPublicationForResourceAsync(int resourceId) { - return await DbContext.ResourceVersion.AsNoTracking() + return await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource) .OrderByDescending(r => r.Id) .FirstOrDefaultAsync(r => r.ResourceId == resourceId @@ -232,7 +232,7 @@ public async Task> GetResourceCards(bool includeEvents) if (includeEvents) { - retVal = await DbContext.ResourceVersion.AsNoTracking() + retVal = await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource).AsNoTracking() .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -245,7 +245,7 @@ public async Task> GetResourceCards(bool includeEvents) } else { - retVal = await DbContext.ResourceVersion.AsNoTracking() + retVal = await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource).AsNoTracking() .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -268,7 +268,7 @@ public async Task GetCurrentForResourceReferenceIdAsync(int res { ResourceVersion retVal = null; - var or = DbContext.ResourceReference.OrderByDescending(r => r.Id).FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); + var or = this.DbContext.ResourceReference.OrderByDescending(r => r.Id).FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); if (or != null) { @@ -287,7 +287,7 @@ public async Task GetCurrentResourceForResourceReferenceIdAsync { ResourceVersion retVal = null; - var or = DbContext.ResourceReference.OrderByDescending(r => r.Id).FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); + var or = this.DbContext.ResourceReference.OrderByDescending(r => r.Id).FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); if (or != null) { @@ -306,7 +306,7 @@ public async Task GetCurrentPublicationForResourceReferenceIdAs { ResourceVersion retVal = null; - var rr = DbContext.ResourceReference.FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); + var rr = this.DbContext.ResourceReference.FirstOrDefault(x => x.OriginalResourceReferenceId == resourceReferenceId); if (rr != null) { @@ -323,7 +323,7 @@ public async Task GetCurrentPublicationForResourceReferenceIdAs /// The . public async Task> GetResourceVersionsAsync(int resourceId) { - return await DbContext.ResourceVersion.AsNoTracking() + return await this.DbContext.ResourceVersion.AsNoTracking() .Include(r => r.Resource).AsNoTracking() .Include(r => r.Publication).AsNoTracking() .Include(r => r.ResourceVersionKeyword).AsNoTracking() @@ -344,7 +344,7 @@ public async Task> GetResourceVersionsAsync(int resourceId /// The . public new async Task UpdateAsync(int userId, ResourceVersion resourceVersion) { - var resourceVersionUpdate = DbContext.ResourceVersion + var resourceVersionUpdate = this.DbContext.ResourceVersion .SingleOrDefault(r => r.Id == resourceVersion.Id); if (resourceVersionUpdate != null) @@ -361,7 +361,7 @@ public async Task> GetResourceVersionsAsync(int resourceId SetAuditFieldsForUpdate(userId, resourceVersionUpdate); } - await DbContext.SaveChangesAsync(); + await this.DbContext.SaveChangesAsync(); } /// @@ -372,7 +372,7 @@ public async Task> GetResourceVersionsAsync(int resourceId /// The . public async Task UpdateDevIdAsync(int userId, ResourceVersionDevIdViewModel resourceVersionDevIdViewModel) { - var resourceVersionUpdate = DbContext.ResourceVersion + var resourceVersionUpdate = this.DbContext.ResourceVersion .SingleOrDefault(r => r.Id == resourceVersionDevIdViewModel.ResourceVersionId); if (resourceVersionUpdate != null) @@ -382,7 +382,7 @@ public async Task UpdateDevIdAsync(int userId, ResourceVersionDevIdViewModel res SetAuditFieldsForUpdate(userId, resourceVersionUpdate); } - await DbContext.SaveChangesAsync(); + await this.DbContext.SaveChangesAsync(); } /// @@ -398,7 +398,7 @@ public void SetResourceType(int resourceVersionId, ResourceTypeEnum resourceType var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; - DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionSetResourceType @p0, @p1, @p2, @p3", param0, param1, param2, param3); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionSetResourceType @p0, @p1, @p2, @p3", param0, param1, param2, param3); } /// @@ -408,7 +408,7 @@ public void SetResourceType(int resourceVersionId, ResourceTypeEnum resourceType /// The . public async Task GetResourceType(int resourceVersionId) { - return await DbContext.ResourceVersion + return await this.DbContext.ResourceVersion .Include(rv => rv.Resource).AsNoTracking() .Where(rv => rv.Id == resourceVersionId) .Select(rv => rv.Resource.ResourceTypeEnum) @@ -442,7 +442,7 @@ public int Publish(int resourceVersionId, bool isMajorRevision, string notes, Da sql += ", @PublicationDate=@p6"; } - DbContext.Database.ExecuteSqlRaw(sql, sqlParams); + this.DbContext.Database.ExecuteSqlRaw(sql, sqlParams); return (int)param5.Value; } @@ -462,7 +462,7 @@ public void Unpublish(int resourceVersionId, string details, int userId) var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userId }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; - DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionUnpublish @p0, @p1, @p2, @p3", param0, param1, param2, param3); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionUnpublish @p0, @p1, @p2, @p3", param0, param1, param2, param3); } /// @@ -476,7 +476,7 @@ public void RevertToDraft(int resourceVersionId, int userId) 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("resources.ResourceVersionRevertToDraft @p0, @p1, @p2", param0, param1, param2); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionRevertToDraft @p0, @p1, @p2", param0, param1, param2); } /// @@ -490,7 +490,7 @@ public void Delete(int resourceVersionId, int userId) 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("resources.ResourceVersionDelete @p0, @p1, @p2", param0, param1, param2); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionDelete @p0, @p1, @p2", param0, param1, param2); } /// @@ -501,7 +501,7 @@ public void Delete(int resourceVersionId, int userId) /// The . public Task DoesVersionExist(int resourceId, VersionStatusEnum status) { - return DbContext.ResourceVersion.AnyAsync(rv => rv.ResourceId == resourceId && rv.VersionStatusEnum == status); + return this.DbContext.ResourceVersion.AnyAsync(rv => rv.ResourceId == resourceId && rv.VersionStatusEnum == status); } /// @@ -517,7 +517,7 @@ public async Task CreateNextVersionAsync(int resourceId, int userId) var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Direction = ParameterDirection.Output }; - await DbContext.Database.ExecuteSqlRawAsync("resources.ResourceVersionCreateNext @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); + await this.DbContext.Database.ExecuteSqlRawAsync("resources.ResourceVersionCreateNext @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); return (int)param3.Value; } @@ -535,7 +535,7 @@ public async Task CreateDuplicateVersionAsync(int resourceId, int userId) var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Direction = ParameterDirection.Output }; - await DbContext.Database.ExecuteSqlRawAsync("resources.ResourceVersionCreateDuplicate @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); + await this.DbContext.Database.ExecuteSqlRawAsync("resources.ResourceVersionCreateDuplicate @p0, @p1, @p2, @p3 output", param0, param1, param2, param3); return (int)param3.Value; } @@ -567,7 +567,7 @@ public async Task CreateDuplicateVersionAsync(int resourceId, int userId) //// var param4 = new SqlParameter("@p4", SqlDbType.DateTimeOffset) { Value = activityStart }; //// var param5 = new SqlParameter("@p5", SqlDbType.DateTimeOffset) { Value = activityEnd }; - //// this.DbContext.Database.ExecuteSqlCommand("activity.ResourceActivityCreate @p0, @p1, @p2, @p3, @p4, @p5", param0, param1, param2, param3, param4, param5); + //// this.this.DbContext.Database.ExecuteSqlCommand("activity.ResourceActivityCreate @p0, @p1, @p2, @p3, @p4, @p5", param0, param1, param2, param3, param4, param5); ////} /// @@ -578,7 +578,7 @@ public async Task CreateDuplicateVersionAsync(int resourceId, int userId) /// A boolean. public async Task HasUserCompletedActivity(int userId, int resourceVersionId) { - return await DbContext.ResourceActivity.AnyAsync(x => x.UserId == userId && x.ResourceVersionId == resourceVersionId); + return await this.DbContext.ResourceActivity.AnyAsync(x => x.UserId == userId && x.ResourceVersionId == resourceVersionId); } /// @@ -592,7 +592,7 @@ public void SubmitForPublishing(int resourceVersionId, int userId) 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("resources.ResourceVersionSubmitForPublishing @p0, @p1, @p2", param0, param1, param2); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionSubmitForPublishing @p0, @p1, @p2", param0, param1, param2); } /// @@ -605,7 +605,7 @@ public void Publishing(int resourceVersionId, int userId) var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = resourceVersionId }; var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; - DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionPublishing @p0, @p1", param0, param1); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionPublishing @p0, @p1", param0, param1); } /// @@ -618,7 +618,7 @@ public void FailedToPublish(int resourceVersionId, int userId) var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = resourceVersionId }; var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userId }; - DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionFailedToPublish @p0, @p1", param0, param1); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionFailedToPublish @p0, @p1", param0, param1); } /// @@ -636,7 +636,7 @@ public void CreateResourceVersionEvent(int resourceVersionId, ResourceVersionEve var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = userId }; var param4 = new SqlParameter("@p4", SqlDbType.Int) { Value = TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; - DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionEventCreate @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4); + this.DbContext.Database.ExecuteSqlRaw("resources.ResourceVersionEventCreate @p0, @p1, @p2, @p3, @p4", param0, param1, param2, param3, param4); } /// @@ -658,7 +658,7 @@ public MyContributionsTotalsViewModel GetMyContributionTotals(int catalogueNodeI var param8 = new SqlParameter("@UnpublishedCount", SqlDbType.Int) { Direction = ParameterDirection.Output }; var param9 = new SqlParameter("@UserUnpublishedCount", SqlDbType.Int) { Direction = ParameterDirection.Output }; - DbContext.Database.ExecuteSqlRaw("resources.GetContributionTotals @catalogueNodeId, @userId, @ActionRequiredCount output, @UserActionRequiredCount output, @DraftCount output, @UserDraftCount output, @PublishedCount output, @UserPublishedCount output, @UnpublishedCount output, @UserUnpublishedCount output", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9); + this.DbContext.Database.ExecuteSqlRaw("resources.GetContributionTotals @catalogueNodeId, @userId, @ActionRequiredCount output, @UserActionRequiredCount output, @DraftCount output, @UserDraftCount output, @PublishedCount output, @UserPublishedCount output, @UnpublishedCount output, @UserUnpublishedCount output", param0, param1, param2, param3, param4, param5, param6, param7, param8, param9); var myContributionsTotalsViewModel = new MyContributionsTotalsViewModel(); @@ -711,7 +711,7 @@ public List GetContributions(int userId, ResourceContri sql += " @catalogueNodeId, @userId, @offset, @take, @restrictToCurrentUser"; - var contributions = DbContext.ResourceContributionDto.FromSqlRaw(sql, param0, param1, param2, param3, param4).ToList(); + var contributions = this.DbContext.ResourceContributionDto.FromSqlRaw(sql, param0, param1, param2, param3, param4).ToList(); return contributions; } @@ -732,22 +732,22 @@ public List GetContributions(int userId, ResourceContri switch (dashboardType) { case "my-certificates": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyLearningCertificatesDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyLearningCertificatesDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "my-recent-completed": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "my-in-progress": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyInProgressDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyInProgressDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "recent-resources": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "rated-resources": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "popular-resources": - dashboardResources = DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; } @@ -773,7 +773,7 @@ public async Task FractionalDuplication(int userId, int sourceBlockCollecti var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = destinationBlockCollectionId }; var param2 = new SqlParameter("@p2", SqlDbType.Structured) { Value = ids, TypeName = "resources.IDList" }; var param5 = new SqlParameter("@p5", SqlDbType.Int) { Direction = ParameterDirection.Output }; - await DbContext.Database.ExecuteSqlRawAsync( + await this.DbContext.Database.ExecuteSqlRawAsync( "resources.BlockCollectionWithBlocksCreateDuplicate @p0, @p1, @p2, @p3, @p4, @p5 output", param0, param1, param2, param3, param4, param5); return param5.Value == DBNull.Value ? -1 : (int)param5.Value; } @@ -791,7 +791,7 @@ public async Task GetExternalContentDetails(int var param0 = new SqlParameter("@resourceVersionId", SqlDbType.Int) { Value = resourceVersionId }; var param1 = new SqlParameter("@userId", SqlDbType.Int) { Value = userId }; - var retVal = await DbContext.ExternalContentDetailsViewModel.FromSqlRaw("[resources].[GetExternalContentDetails] @resourceVersionId, @userId", param0, param1).AsNoTracking().ToListAsync(); + var retVal = await this.DbContext.ExternalContentDetailsViewModel.FromSqlRaw("[resources].[GetExternalContentDetails] @resourceVersionId, @userId", param0, param1).AsNoTracking().ToListAsync(); ExternalContentDetailsViewModel externalContentDetailsViewModel = retVal.AsEnumerable().FirstOrDefault(); return externalContentDetailsViewModel; } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionValidationResultRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionValidationResultRepository.cs index edeb8a4c6..f6d06a03a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionValidationResultRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionValidationResultRepository.cs @@ -58,7 +58,7 @@ public async Task GetByResourceVersionIdAsync(i .Include(r => r.ResourceVersion).AsNoTracking() .Include(r => r.ResourceVersionValidationRuleResults).AsNoTracking() .Include(r => r.CreateUser).AsNoTracking() - .Where(r => r.ResourceVersionId == resourceVersionId && r.ResourceVersion.VersionStatusEnum == LearningHub.Nhs.Models.Enums.VersionStatusEnum.FailedToPublish) + .Where(r => r.ResourceVersionId == resourceVersionId && r.ResourceVersion.VersionStatusEnum == Nhs.Models.Enums.VersionStatusEnum.FailedToPublish) .OrderByDescending(r => r.Id) .FirstOrDefaultAsync(); } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserNotificationRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserNotificationRepository.cs new file mode 100644 index 000000000..02e67126a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/UserNotificationRepository.cs @@ -0,0 +1,106 @@ +namespace LearningHub.Nhs.OpenApi.Repositories.Repositories +{ + using System; + 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 notification repository. + /// + public class UserNotificationRepository : GenericRepository, IUserNotificationRepository + { + /// + /// Initializes a new instance of the class. + /// + /// The db context. + /// The Timezone offset manager. + public UserNotificationRepository(LearningHubDbContext dbContext, ITimezoneOffsetManager tzOffsetManager) + : base(dbContext, tzOffsetManager) + { + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await DbContext.UserNotification + .Where(n => n.Id == id).SingleOrDefaultAsync(); + } + + /// + /// The get user unread notification count async. + /// + /// The user id. + /// The . + public async Task GetUserUnreadNotificationCountAsync(int userId) + { + return await GetActiveNotifications(userId, DateTimeOffset.Now) + .Where(n => !n.UserNotification.ReadOnDate.HasValue) + .CountAsync(); + } + + /// + /// The get all non dismissed. + /// + /// The user id. + /// Notification priority type. + /// The sort column. + /// The sort direction. + /// The . + public IQueryable GetAllNonDismissed(int userId, NotificationPriorityEnum priorityType, string sortColumn = "", string sortDirection = "") + { + var query = GetActiveNotifications(userId, DateTimeOffset.Now) + .Where(n => n.Notification.NotificationPriorityEnum == priorityType); + + if (!string.IsNullOrWhiteSpace(sortColumn) && !string.IsNullOrWhiteSpace(sortDirection)) + { + switch (sortColumn) + { + case "date": + return sortDirection == "A" ? query.OrderBy(n => n.Notification.StartDate) : query.OrderByDescending(n => n.Notification.StartDate); + default: + throw new NotImplementedException($"Sorting for column:{sortColumn} on notifications not implemented."); + } + } + + return query.OrderByDescending(n => n.Notification.StartDate); + } + + /// + /// The get by notification and user id async. + /// + /// The user id. + /// The notification id. + /// The . + public async Task GetByNotificationAndUserIdAsync(int userId, int notificationId) + { + return await DbContext.UserNotification + .Include(n => n.AmendUser) + .Include(n => n.CreateUser) + .Include(n => n.User) + .Include(n => n.Notification) + .Where(n => n.NotificationId == notificationId && n.UserId == userId).SingleOrDefaultAsync(); + } + + private IQueryable GetActiveNotifications(int userId, DateTimeOffset now) + { + return from n in DbContext.Notification + join un in DbContext.UserNotification + on new { n.Id, UserId = userId } + equals new { Id = un.NotificationId, un.UserId } into list + from un in list.DefaultIfEmpty() + where now >= n.StartDate && now <= n.EndDate + && (n.IsUserSpecific == false || un.UserId == userId) + && (un == null || un.Dismissed == false) + select new UserSpecificNotification { Notification = n, UserNotification = un }; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs index f6d13500b..3a32a35f6 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Startup.cs @@ -51,6 +51,8 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // Resources services.AddScoped(); @@ -69,11 +71,13 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -83,7 +87,9 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); + // Activity services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -92,10 +98,6 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); - // Activity - services.AddScoped(); - services.AddScoped(); - // Hierarchy services.AddScoped(); services.AddScoped(); @@ -112,7 +114,6 @@ private static void AddRepositoryImplementations(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); - } } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IHierarchyService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IHierarchyService.cs index 2a8efec95..214768c1f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IHierarchyService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IHierarchyService.cs @@ -54,6 +54,15 @@ public interface IHierarchyService /// The . Task GetCatalogueLocationsForResourceReference(int resourceReferenceId); + /// + /// 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. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INavigationPermissionService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INavigationPermissionService.cs new file mode 100644 index 000000000..7095bc713 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INavigationPermissionService.cs @@ -0,0 +1,27 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Security.Principal; + using System.Threading.Tasks; + using LearningHub.Nhs.OpenApi.Models.ViewModels; + + /// + /// Defines the . + /// + public interface INavigationPermissionService + { + /// + /// The GetNavigationModelAsync. + /// + /// User. + /// Login wizard complete. + /// Controller name. + /// A representing the result of the asynchronous operation. + Task GetNavigationModelAsync(IPrincipal user, bool loginWizardComplete, string controllerName); + + /// + /// The NotAuthenticated. + /// + /// The . + NavigationModel NotAuthenticated(); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs new file mode 100644 index 000000000..0a0af21eb --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/INotificationService.cs @@ -0,0 +1,73 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Notification; + using LearningHub.Nhs.Models.Validation; + + /// + /// The NotificationService interface. + /// + public interface INotificationService + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get page async. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The filter. + /// The . + Task> GetPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string filter = ""); + + /// + /// The create async. + /// + /// The user id. + /// The notification. + /// The . + Task CreateAsync(int userId, NotificationViewModel notification); + + /// + /// The update async. + /// + /// The user id. + /// The notification. + /// The . + Task UpdateAsync(int userId, NotificationViewModel notification); + + /// + /// Creates resource access permisssion notification. + /// + /// The current user id. + /// Is read only user. + /// The . + Task CreatePermisssionNotificationAsync(int userId, bool readOnlyUser); + + /// + /// Creates resource published notification. + /// + /// The current user id. + /// Resource title. + /// Resource reference id. + /// The . + Task CreateResourcePublishedNotificationAsync(int userId, string resourceTitle, int resourceReferenceId); + + /// + /// Creates resource publish failed notification. + /// + /// The current user id. + /// Resource title. + /// Error message. + /// The . + Task CreatePublishFailedNotificationAsync(int userId, string resourceTitle, string errorMessage = ""); + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IRatingService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IRatingService.cs new file mode 100644 index 000000000..d6579b983 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IRatingService.cs @@ -0,0 +1,44 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Validation; + + /// + /// The RatingService interface. + /// + public interface IRatingService + { + /// + /// Returns the rating summary for a given entity. + /// + /// The user id. + /// The entity version id. + /// The . + Task GetRatingSummary(int userId, int entityVersionId); + + /// + /// Returns the basic rating summary for a given entity. + /// Does not include user related details. + /// + /// The entity version id. + /// The . + Task GetRatingSummaryBasic(int entityVersionId); + + /// + /// Create a rating of an entity. Can only be performed once per major version of an entity. + /// + /// The user Id. + /// The ratingViewModel. + /// The . + Task CreateRating(int userId, RatingViewModel ratingViewModel); + + /// + /// Update a rating of an entity. Can be performed on ratings for any minor version of a major version of the resource. + /// + /// The user Id. + /// The ratingViewModel. + /// The . + Task UpdateRating(int userId, RatingViewModel ratingViewModel); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs index 6c0b59eb8..a2628f478 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceService.cs @@ -278,6 +278,13 @@ Task GetAssessmentProgress( /// The . Task GetFileAsync(int fileId); + /// + /// The get image details by id async. + /// + /// The resourceVersionId. + /// The . + Task GetImageDetailsByIdAsync(int resourceVersionId); + /// /// The create file details for an article async. /// @@ -350,6 +357,14 @@ Task GetAssessmentProgress( /// The . Task SubmitResourceVersionForPublish(PublishViewModel publishViewModel); + /// + /// Submits a published resource version to the Findwise search. + /// + /// The resourceVersionId. + /// The userId. + /// The . + Task<(bool success, int resourceReferenceId)> SubmitResourceVersionToSearchAsync(int resourceVersionId, int userId); + /// /// The update resource version async. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceSyncService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceSyncService.cs new file mode 100644 index 000000000..6625bec7c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IResourceSyncService.cs @@ -0,0 +1,67 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Validation; + + /// + /// The IResourceSyncService. + /// + public interface IResourceSyncService + { + /// + /// The BuildSearchResourceRequestModel. + /// + /// The resource version id. + /// The task. + Task BuildSearchResourceRequestModel(int resourceVersionId); + + /// + /// The AddToSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + Task AddToSyncListAsync(int userId, List resourceIds); + + /// + /// The RemoveFromSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + Task RemoveFromSyncListAsync(int userId, List resourceIds); + + /// + /// The GetSyncListResourcesForUser. + /// + /// The userId. + /// The list of resources. + List GetSyncListResourcesForUser(int userId); + + /// + /// The GetSyncListForUser. + /// + /// The userId. + /// If the resource property should be populated. + /// The resource syncs. + List GetSyncListForUser(int userId, bool includeResources); + + /// + /// The SyncForUserAsync. + /// + /// The userId. + /// The task. + Task SyncForUserAsync(int userId); + + /// + /// The sync single async. + /// + /// The resourceVersionId. + /// The task. + Task SyncSingleAsync(int resourceVersionId); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs index 8061f94da..f881187d9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/ISearchService.cs @@ -37,5 +37,15 @@ public interface ISearchService /// The resource id. /// The . Task RemoveResourceFromSearchAsync(int resourceId); + + /// + /// The send resource for search Async method. + /// + /// The search resource model. + /// The user id. + /// number of iterations. + /// The . + Task SendResourceForSearchAsync(SearchResourceRequestModel searchResourceRequestModel, int userId, int? iterations); + } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserNotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserNotificationService.cs new file mode 100644 index 000000000..d0ac7d80c --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/Services/IUserNotificationService.cs @@ -0,0 +1,65 @@ +namespace LearningHub.Nhs.OpenApi.Services.Interface.Services +{ + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Notification; + using LearningHub.Nhs.Models.Validation; + + /// + /// The UserNotificationService interface. + /// + public interface IUserNotificationService + { + /// + /// The get by id async. + /// + /// The id. + /// The . + Task GetByIdAsync(int id); + + /// + /// The get by id async. + /// + /// The notificationId. + /// The userId. + /// The . + Task GetByIdAndUserIdAsync(int notificationId, int userId); + + /// + /// The get page async. + /// + /// The user id. + /// Notification priority type. + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The . + Task> GetPageAsync(int userId, NotificationPriorityEnum priorityType, int page, int pageSize, string sortColumn = "", string sortDirection = ""); + + /// + /// The get user unread notification count async. + /// + /// The userid. + /// The . + Task GetUserUnreadNotificationCountAsync(int userid); + + /// + /// The update async. + /// + /// The user id. + /// The notification. + /// The . + Task UpdateAsync(int userId, UserNotification notification); + + /// + /// The create async. + /// + /// The user id. + /// The notification. + /// The . + Task CreateAsync(int userId, UserNotification notification); + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ActivityService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ActivityService.cs index 41cc9b610..75b898af8 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ActivityService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ActivityService.cs @@ -830,7 +830,7 @@ private bool AssessmentResourceActivityIsFinished(AssessmentResourceActivity act /// The task that resolves to true if the selection is valid. private async Task UserAnswerSelectionIsValid(int userId, bool answerInOrder, AssessmentResourceActivity assessmentResourceActivity, CreateAssessmentResourceActivityInteractionViewModel createAssessmentResourceActivityInteractionViewModel) { - return !answerInOrder || await UserHasAnsweredQuestionsUpTo(userId, assessmentResourceActivity.Id, createAssessmentResourceActivityInteractionViewModel.QuestionNumber); + return !answerInOrder || await this.UserHasAnsweredQuestionsUpTo(userId, assessmentResourceActivity.Id, createAssessmentResourceActivityInteractionViewModel.QuestionNumber); } /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs index 158f94e23..19f06d56a 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/CatalogueService.cs @@ -76,7 +76,7 @@ public class CatalogueService : ICatalogueService /// /// /// - public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepository nodeRepository, IUserUserGroupRepository userUserGroupRepository, IMapper mapper, IOptions findwiseConfig, IOptions learningHubConfig, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, INodeResourceRepository nodeResourceRepository, IResourceVersionRepository resourceVersionRepository, IRoleUserGroupRepository roleUserGroupRepository, IProviderService providerService, ICatalogueAccessRequestRepository catalogueAccessRequestRepository, IUserRepository userRepository, IUserProfileRepository userProfileRepository, IEmailSenderService emailSenderService, IBookmarkRepository bookmarkRepository,INodeActivityRepository nodeActivityRepository, IFindwiseApiFacade findwiseApiFacade) + public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepository nodeRepository, IUserUserGroupRepository userUserGroupRepository, IMapper mapper, IOptions findwiseConfig, IOptions learningHubConfig, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, INodeResourceRepository nodeResourceRepository, IResourceVersionRepository resourceVersionRepository, IRoleUserGroupRepository roleUserGroupRepository, IProviderService providerService, ICatalogueAccessRequestRepository catalogueAccessRequestRepository, IUserRepository userRepository, IUserProfileRepository userProfileRepository, IEmailSenderService emailSenderService, IBookmarkRepository bookmarkRepository,INodeActivityRepository nodeActivityRepository, IFindwiseApiFacade findwiseApiFacade, INotificationSenderService notificationSenderService, ITimezoneOffsetManager timezoneOffsetManager) { this.catalogueRepository = catalogueRepository; this.nodeRepository = nodeRepository; @@ -96,6 +96,8 @@ public CatalogueService(ICatalogueRepository catalogueRepository, INodeRepositor this.findwiseApiFacade = findwiseApiFacade; this.learningHubConfig = learningHubConfig.Value; this.findwiseConfig = findwiseConfig.Value; + this.timezoneOffsetManager = timezoneOffsetManager; + this.notificationSenderService = notificationSenderService; } /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/HierarchyService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/HierarchyService.cs index 7009920d3..9f2a5b182 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/HierarchyService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/HierarchyService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using AutoMapper; + using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Constants; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Hierarchy; @@ -26,28 +27,26 @@ public class HierarchyService : IHierarchyService /// private readonly IUserService userService; - - /// /// The node repository. /// private readonly INodeRepository nodeRepository; - + /// + /// The rating service. + /// + private readonly IRatingService ratingService; /// /// The node path repository. /// private readonly INodePathRepository nodePathRepository; - - /// /// The folder node version repository. /// private readonly IFolderNodeVersionRepository folderNodeVersionRepository; - - + /// /// The caching service. /// @@ -141,6 +140,7 @@ public HierarchyService( IHierarchyEditRepository hierarchyEditRepository, INodeResourceLookupRepository nodeResourceLookupRepository, IPublicationRepository publicationRepository, + IRatingService ratingService, ICachingService cachingService, ILogger logger, IOptions learningHubConfig, @@ -154,11 +154,14 @@ public HierarchyService( this.queueCommunicatorService = queueCommunicatorService; this.nodeRepository = nodeRepository; this.nodePathRepository = nodePathRepository; + this.nodeResourceRepository = nodeResourceRepository; this.folderNodeVersionRepository = folderNodeVersionRepository; this.hierarchyEditDetailRepository = hierarchyEditDetailRepository; this.hierarchyEditRepository = hierarchyEditRepository; this.publicationRepository = publicationRepository; this.nodeResourceLookupRepository = nodeResourceLookupRepository; + this.resourceReferenceRepository = resourceReferenceRepository; + this.ratingService = ratingService; this.cachingService = cachingService; this.logger = logger; this.mapper = mapper; @@ -253,6 +256,75 @@ public async Task GetCatalogueLocationsForResourceR return vm; } + /// + /// 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) + { + // Attempt to retrieve from cache. If not found then add to cache. + // Note: include basic resource rating information when serving from the cache. + var retVal = new List(); + + string cacheKey = $"{CacheKeys.PublishedNodeContents}:{nodeId}"; + var nodeContents = await this.cachingService.GetAsync>(cacheKey); + if (includeEmptyFolder) + { + nodeContents.ResponseEnum = CacheReadResponseEnum.NotFound; + } + + if (nodeContents.ResponseEnum == CacheReadResponseEnum.Found) + { + retVal = nodeContents.Item; + } + else + { + retVal = await this.nodeRepository.GetNodeContentsForCatalogueBrowse(nodeId, includeEmptyFolder); + + // Ensure that any Node only exists once in the returned data set. + var duplicateNodes = retVal.Where(n => n.NodeId.HasValue).GroupBy(x => x.NodeId).Where(g => g.Count() > 1).Select(y => y.Key).ToList(); + if (duplicateNodes.Count > 0) + { + throw new Exception($"Corrupt data. Duplicate Nodes returned in NodeContent for NodeId={nodeId}"); + } + + if (!includeEmptyFolder) + { + await this.cachingService.SetAsync(cacheKey, retVal); + } + } + + var list = retVal.Where(ncm => ncm.ResourceVersionId.HasValue); + + if (list.Count() > 0) + { + var tasks = list.Select(async ncm => + { + string cacheKey = $"{CacheKeys.RatingSummaryBasic}:{ncm.ResourceVersionId.Value}"; + var retVal = await this.cachingService.GetAsync(cacheKey); + return retVal.ResponseEnum == CacheReadResponseEnum.Found ? retVal.Item : null; + }); + + var ratings = await Task.WhenAll(tasks); + + foreach (var ncm in list.Where(ncm => ncm.ResourceVersionId.HasValue)) + { + var rating = ratings.Where(r => r != null && r.EntityVersionId == ncm.ResourceVersionId.Value).FirstOrDefault(); + if (rating == null) + { + rating = await this.ratingService.GetRatingSummaryBasic(ncm.ResourceVersionId.Value); + } + + ncm.RatingSummaryBasicViewModel = rating; + } + } + + return retVal; + } + /// /// Creates a new folder. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs new file mode 100644 index 000000000..76e66812f --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NavigationPermissionService.cs @@ -0,0 +1,235 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System.Security.Principal; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.OpenApi.Models.ViewModels; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + + /// + /// Defines the . + /// + public class NavigationPermissionService : INavigationPermissionService + { + private readonly IResourceService resourceService; + + /// + /// Initializes a new instance of the class. + /// + /// Resource service. + public NavigationPermissionService(IResourceService resourceService) + { + this.resourceService = resourceService; + } + + /// + /// The GetNavigationModelAsync. + /// + /// The user. + /// The loginWizardComplete. + /// The controller name. + /// The . + public async Task GetNavigationModelAsync(IPrincipal user, bool loginWizardComplete, string controllerName) + { + if (!loginWizardComplete && (user.IsInRole("Administrator") || user.IsInRole("ReadOnly") || user.IsInRole("BlueUser") || user.IsInRole("BasicUser"))) + { + return InLoginWizard(); + } + else if (!user.Identity.IsAuthenticated) + { + return NotAuthenticated(); + } + else if (user.IsInRole("Administrator")) + { + return AuthenticatedAdministrator(controllerName); + } + else if (user.IsInRole("ReadOnly")) + { + return await AuthenticatedReadOnly(controllerName,user.Identity.GetCurrentUserId()); + } + else if (user.IsInRole("BasicUser")) + { + return await AuthenticatedBasicUserOnly(user.Identity.GetCurrentUserId()); + } + else if (user.IsInRole("BlueUser")) + { + return AuthenticatedBlueUser(controllerName); + } + else + { + return AuthenticatedGuest(); + } + } + + /// + /// The NotAuthenticated. + /// + /// The . + public NavigationModel NotAuthenticated() + { + return new NavigationModel() + { + ShowMyContributions = false, + ShowMyLearning = false, + ShowMyBookmarks = false, + ShowSearch = false, + ShowAdmin = false, + ShowForums = false, + ShowHelp = false, + ShowMyRecords = false, + ShowNotifications = false, + ShowRegister = false, + ShowSignOut = false, + ShowMyAccount = false, + ShowBrowseCatalogues = false, + }; + } + + /// + /// The AuthenticatedAdministrator. + /// + /// The controller name. + /// The . + private NavigationModel AuthenticatedAdministrator(string controllerName) + { + return new NavigationModel() + { + ShowMyContributions = true, + ShowMyLearning = true, + ShowMyBookmarks = true, + ShowSearch = controllerName != "search" && controllerName != string.Empty, + ShowAdmin = true, + ShowForums = true, + ShowHelp = true, + ShowMyRecords = true, + ShowNotifications = true, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = true, + ShowBrowseCatalogues = true, + }; + } + + /// + /// The AuthenticatedBlueUser. + /// + /// The controller name. + /// The . + private NavigationModel AuthenticatedBlueUser(string controllerName) + { + return new NavigationModel() + { + ShowMyContributions = true, + ShowMyLearning = true, + ShowMyBookmarks = true, + ShowSearch = controllerName != "search" && controllerName != string.Empty, + ShowAdmin = false, + ShowForums = true, + ShowHelp = true, + ShowMyRecords = true, + ShowNotifications = true, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = true, + ShowBrowseCatalogues = true, + }; + } + + /// + /// The AuthenticatedGuest. + /// + /// The . + private NavigationModel AuthenticatedGuest() + { + return new NavigationModel() + { + ShowMyContributions = false, + ShowMyLearning = false, + ShowMyBookmarks = false, + ShowSearch = false, + ShowAdmin = false, + ShowForums = false, + ShowHelp = true, + ShowMyRecords = false, + ShowNotifications = false, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = false, + ShowBrowseCatalogues = false, + }; + } + + /// + /// The AuthenticatedReadOnly. + /// + /// The controller name. + /// The . + private async Task AuthenticatedReadOnly(string controllerName,int userId) + { + return new NavigationModel() + { + ShowMyContributions = await resourceService.HasPublishedResourcesAsync(userId), + ShowMyLearning = true, + ShowMyBookmarks = true, + ShowSearch = controllerName != "search" && controllerName != string.Empty, + ShowAdmin = false, + ShowForums = true, + ShowHelp = true, + ShowMyRecords = true, + ShowNotifications = true, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = false, + ShowBrowseCatalogues = true, + }; + } + + /// + /// The AuthenticatedBasicUserOnly. + /// + /// The . + private async Task AuthenticatedBasicUserOnly(int userId) + { + return new NavigationModel() + { + ShowMyContributions = await resourceService.HasPublishedResourcesAsync(userId), + ShowMyLearning = true, + ShowMyBookmarks = true, + ShowSearch = true, + ShowAdmin = false, + ShowForums = true, + ShowHelp = true, + ShowMyRecords = true, + ShowNotifications = true, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = true, + ShowBrowseCatalogues = true, + }; + } + + /// + /// The InLoginWizard. + /// + /// The . + private NavigationModel InLoginWizard() + { + return new NavigationModel() + { + ShowMyContributions = false, + ShowMyLearning = false, + ShowMyBookmarks = false, + ShowSearch = false, + ShowAdmin = false, + ShowForums = false, + ShowHelp = true, + ShowMyRecords = false, + ShowNotifications = false, + ShowRegister = false, + ShowSignOut = true, + ShowMyAccount = false, + ShowBrowseCatalogues = false, + }; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs new file mode 100644 index 000000000..fe002695a --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/NotificationService.cs @@ -0,0 +1,355 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Notification; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Options; + using Newtonsoft.Json; + + /// + /// The notification service. + /// + public class NotificationService : INotificationService + { + private readonly IMapper mapper; + private readonly LearningHubConfig learningHubConfig; + private INotificationRepository notificationRepository; + + /// + /// Initializes a new instance of the class. + /// + /// The notification repository. + /// The settings. + /// The mapper. + public NotificationService( + INotificationRepository notificationRepository, + IOptions learningHubConfig, + IMapper mapper) + { + this.notificationRepository = notificationRepository; + this.learningHubConfig = learningHubConfig.Value; + this.mapper = mapper; + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + var notification = await notificationRepository.GetByIdAsync(id); + + return mapper.Map(notification); + } + + /// + /// The get page async. + /// + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The filter. + /// The . + public async Task> GetPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string filter = "") + { + var filterCriteria = JsonConvert.DeserializeObject>(filter); + + PagedResultSet result = new PagedResultSet(); + + var items = notificationRepository.GetAllFull() + .Where(n => new[] { NotificationTypeEnum.SystemUpdate, NotificationTypeEnum.SystemRelease }.Contains(n.NotificationTypeEnum)); + + if (filterCriteria != null) + { + items = this.FilterItems(items, filterCriteria); + } + + result.TotalItemCount = items.Count(); + + items = this.OrderItems(items, sortColumn, sortDirection); + + items = items.Skip((page - 1) * pageSize).Take(pageSize); + + result.Items = await mapper.ProjectTo(items).ToListAsync(); + + return result; + } + + /// + /// The create async. + /// + /// The user id. + /// The notification. + /// The . + public async Task CreateAsync(int userId, NotificationViewModel notification) + { + var n = mapper.Map(notification); + + var retVal = await ValidateAsync(n); + + if (retVal.IsValid) + { + retVal.CreatedId = await notificationRepository.CreateAsync(userId, n); + } + + return retVal; + } + + /// + /// The update async. + /// + /// The user id. + /// The notification. + /// The . + public async Task UpdateAsync(int userId, NotificationViewModel notification) + { + var n = mapper.Map(notification); + + var retVal = await ValidateAsync(n); + + if (retVal.IsValid) + { + await notificationRepository.UpdateAsync(userId, n); + } + + return retVal; + } + + /// + /// The validate async. + /// + /// The notification. + /// The . + public async Task ValidateAsync(Notification notification) + { + var notificationValidator = new NotificationValidator(); + var clientValidationResult = await notificationValidator.ValidateAsync(notification); + + var retVal = new LearningHubValidationResult(clientValidationResult); + + return retVal; + } + + /// + /// Creates resource access permisssion notification. + /// + /// The current user id. + /// Is read only user. + /// The . + public async Task CreatePermisssionNotificationAsync(int userId, bool readOnlyUser) + { + var title = learningHubConfig.Notifications.ResourceAccessTitle; + + var message = (readOnlyUser ? learningHubConfig.Notifications.ResourceReadonlyAccess : learningHubConfig.Notifications.ResourceContributeAccess) + .Replace("[SupportContact]", learningHubConfig.SupportForm); + + var notification = await CreateAsync(userId, this.UserSpecificNotification( + title, message, NotificationTypeEnum.UserPermission, NotificationPriorityEnum.Priority)); + + if (notification.CreatedId.HasValue) + { + return notification.CreatedId.Value; + } + else + { + return 0; + } + } + + /// + /// Creates resource published notification. + /// + /// The current user id. + /// Notification title. + /// Resource reference id. + /// The . + public async Task CreateResourcePublishedNotificationAsync(int userId, string resourceTitle, int resourceReferenceId) + { + var title = learningHubConfig.Notifications.ResourcePublishedTitle + resourceTitle; + var message = learningHubConfig.Notifications.ResourcePublished + .Replace("[ResourceReferenceId]", resourceReferenceId.ToString()); + + var notification = await CreateAsync(userId, this.UserSpecificNotification( + title, message, NotificationTypeEnum.ResourcePublished, NotificationPriorityEnum.General)); + + if (notification.CreatedId.HasValue) + { + return notification.CreatedId.Value; + } + else + { + return 0; + } + } + + /// + /// Creates resource publish failed notification. + /// + /// The current user id. + /// Resource title. + /// Error message. + /// The . + public async Task CreatePublishFailedNotificationAsync(int userId, string resourceTitle, string errorMessage = "") + { + var title = this.learningHubConfig.Notifications.ResourcePublishFailedTitle + resourceTitle; + string message; + if (errorMessage != string.Empty) + { + message = learningHubConfig.Notifications.ResourcePublishFailedWithReason + .Replace("[ResourceTitle]", resourceTitle) + .Replace("[SupportContact]", learningHubConfig.SupportForm) + .Replace("[ErrorMessage]", errorMessage); + } + else + { + message = learningHubConfig.Notifications.ResourcePublishFailed + .Replace("[ResourceTitle]", resourceTitle) + .Replace("[SupportContact]", learningHubConfig.SupportForm); + } + + var notification = await CreateAsync(userId, this.UserSpecificNotification( + title, message, NotificationTypeEnum.PublishFailed, NotificationPriorityEnum.Priority)); + + if (notification.CreatedId.HasValue) + { + return notification.CreatedId.Value; + } + else + { + return 0; + } + } + + /// + /// The filter items. + /// + /// The items. + /// The filter criteria. + /// The . + private IQueryable FilterItems(IQueryable items, List filterCriteria) + { + if (filterCriteria.Count == 0) + { + return items; + } + + foreach (var filter in filterCriteria) + { + switch (filter.Column) + { + case "Title": + items = items.Where(l => l.Title.Contains(filter.Value)); + break; + case "Message": + items = items.Where(l => l.Message.Contains(filter.Value)); + break; + case "StartDate": + if (DateTime.TryParse(filter.Value, out var st)) + { + items = items.Where(l => l.StartDate >= st && l.StartDate < st.AddDays(1)); + } + + break; + case "EndDate": + if (DateTime.TryParse(filter.Value, out var ed)) + { + items = items.Where(l => l.EndDate >= ed && l.EndDate < ed.AddDays(1)); + } + + break; + case "CreatedBy": + items = items.Where(l => l.CreateUser.UserName.Contains(filter.Value)); + break; + case "NotificationPriority": + if (int.TryParse(filter.Value, out var np)) + { + items = items.Where(l => l.NotificationPriorityEnum == (NotificationPriorityEnum)np); + } + + break; + case "NotificationType": + var types = filter.Value.Split(",") + .Select(t => int.TryParse(t, out var nt) ? (NotificationTypeEnum)nt : default) + .Where(t => t != default).ToList(); + if (types.Count > 0) + { + items = items.Where(l => types.Contains(l.NotificationTypeEnum)); + } + + break; + default: + break; + } + } + + return items; + } + + /// + /// The order items. + /// + /// The items. + /// The sort column. + /// The sort direction. + /// The . + private IQueryable OrderItems(IQueryable items, string sortColumn, string sortDirection) + { + var descending = sortDirection == "D"; + + switch (sortColumn) + { + case "Title": + items = descending ? items.OrderByDescending(l => l.Title) : items.OrderBy(l => l.Title); + break; + case "StartDate": + items = descending ? items.OrderByDescending(l => l.StartDate) : items.OrderBy(l => l.StartDate); + break; + case "EndDate": + items = descending ? items.OrderByDescending(l => l.EndDate) : items.OrderBy(l => l.EndDate); + break; + case "CreatedBy": + items = descending ? items.OrderByDescending(l => l.CreateUser.UserName) : items.OrderBy(l => l.CreateUser.UserName); + break; + case "NotificationPriority": + items = descending ? items.OrderByDescending(l => l.NotificationPriorityEnum) : items.OrderBy(l => l.NotificationPriorityEnum); + break; + case "NotificationType": + items = descending ? items.OrderByDescending(l => l.NotificationTypeEnum) : items.OrderBy(l => l.NotificationTypeEnum); + break; + default: + items = descending ? items.OrderByDescending(l => l.Id) : items.OrderBy(l => l.Id); + break; + } + + return items; + } + + private NotificationViewModel UserSpecificNotification(string title, string message, NotificationTypeEnum type, NotificationPriorityEnum priority) + { + return new NotificationViewModel + { + Title = title, + Message = message, + NotificationType = type, + NotificationPriority = priority, + StartDate = DateTimeOffset.Now, + EndDate = DateTimeOffset.MaxValue, + UserDismissable = true, + IsUserSpecific = true, + }; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/RatingService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/RatingService.cs new file mode 100644 index 000000000..2a1707c36 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/RatingService.cs @@ -0,0 +1,365 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Constants; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.Extensions.Logging; + + /// + /// The rating service. + /// + public class RatingService : IRatingService + { + /// + /// The user service. + /// + private readonly IUserService userService; + + /// + /// The resource service. + /// + private readonly IResourceService resourceService; + + /// + /// The resource version repository. + /// + private readonly IResourceVersionRepository resourceVersionRepository; + + /// + /// The resource repository. + /// + private readonly IResourceRepository resourceRepository; + + /// + /// The resource version rating summary repository. + /// + private readonly IResourceVersionRatingSummaryRepository resourceVersionRatingSummaryRepository; + + /// + /// The resource version rating repository. + /// + private readonly IResourceVersionRatingRepository resourceVersionRatingRepository; + + /// + /// The publication repository. + /// + private readonly IPublicationRepository publicationRepository; + + /// + /// The caching service. + /// + private readonly ICachingService cachingService; + + /// + /// The logger. + /// + private readonly ILogger logger; + + /// + /// The mapper. + /// + private readonly IMapper mapper; + + /// + /// Initializes a new instance of the class. + /// + /// The user service. + /// The resource service. + /// The resource version repository. + /// The resource repository. + /// The resource version rating summary repository. + /// The resource version rating repository. + /// The publicationRepository. + /// The caching service. + /// The logger. + /// The mapper. + public RatingService( + IUserService userService, + IResourceService resourceService, + IResourceVersionRepository resourceVersionRepository, + IResourceRepository resourceRepository, + IResourceVersionRatingSummaryRepository resourceVersionRatingSummaryRepository, + IResourceVersionRatingRepository resourceVersionRatingRepository, + ////IUserEmploymentRepository userEmploymentRepository, + IPublicationRepository publicationRepository, + ICachingService cachingService, + ILogger logger, + IMapper mapper) + { + this.userService = userService; + this.resourceService = resourceService; + this.resourceVersionRepository = resourceVersionRepository; + this.resourceRepository = resourceRepository; + this.resourceVersionRatingSummaryRepository = resourceVersionRatingSummaryRepository; + this.resourceVersionRatingRepository = resourceVersionRatingRepository; + ////this.userEmploymentRepository = userEmploymentRepository; + this.publicationRepository = publicationRepository; + this.cachingService = cachingService; + this.logger = logger; + this.mapper = mapper; + } + + /// + /// Returns the rating summary for a given entity. + /// + /// The user id. + /// The entity version id. + /// The . + public async Task GetRatingSummary(int userId, int entityVersionId) + { + var ratingSummary = await resourceVersionRatingSummaryRepository.GetByResourceVersionIdAsync(entityVersionId); + RatingSummaryViewModel ratingSummaryViewModel; + + if (ratingSummary != null) + { + ratingSummaryViewModel = mapper.Map(ratingSummary); + } + else + { + ratingSummaryViewModel = new RatingSummaryViewModel + { + AverageRating = 0, + RatingCount = 0, + Rating1StarPercent = 0, + Rating2StarPercent = 0, + Rating3StarPercent = 0, + Rating4StarPercent = 0, + Rating5StarPercent = 0, + UserIsContributor = false, + UserCanRate = false, + UserRating = 0, + }; + } + + if (ratingSummaryViewModel.RatingCount > 0) + { + ratingSummaryViewModel.Rating1StarPercent = (int)((double)ratingSummary.Rating1StarCount / ratingSummary.RatingCount * 100); + ratingSummaryViewModel.Rating2StarPercent = (int)((double)ratingSummary.Rating2StarCount / ratingSummary.RatingCount * 100); + ratingSummaryViewModel.Rating3StarPercent = (int)((double)ratingSummary.Rating3StarCount / ratingSummary.RatingCount * 100); + ratingSummaryViewModel.Rating4StarPercent = (int)((double)ratingSummary.Rating4StarCount / ratingSummary.RatingCount * 100); + ratingSummaryViewModel.Rating5StarPercent = (int)((double)ratingSummary.Rating5StarCount / ratingSummary.RatingCount * 100); + } + + ratingSummaryViewModel.UserIsContributor = await IsTheUserTheContributor(userId, entityVersionId); + ratingSummaryViewModel.UserCanRate = await HasUserCompletedActivity(userId, entityVersionId); + + var userRating = await resourceVersionRatingRepository.GetUsersPreviousRatingForSameMajorVersionAsync(entityVersionId, userId); + if (userRating != null) + { + ratingSummaryViewModel.UserRating = userRating.Rating; + ratingSummaryViewModel.UserHasAlreadyRated = true; + } + + return ratingSummaryViewModel; + } + + /// + /// Returns the basic rating summary for a given entity. + /// Does not include user related details. + /// + /// The entity version id. + /// The . + public async Task GetRatingSummaryBasic(int entityVersionId) + { + string cacheKey = $"{CacheKeys.RatingSummaryBasic}:{entityVersionId}"; + var retVal = await cachingService.GetAsync(cacheKey); + if (retVal.ResponseEnum == CacheReadResponseEnum.Found) + { + return retVal.Item; + } + else + { + try + { + var ratingSummary = await resourceVersionRatingSummaryRepository.GetByResourceVersionIdAsync(entityVersionId); + + var ratingSummaryBasicViewModel = mapper.Map(ratingSummary); + + ratingSummaryBasicViewModel.Rating1StarPercent = (int)((double)ratingSummary.Rating1StarCount / ratingSummary.RatingCount * 100); + ratingSummaryBasicViewModel.Rating2StarPercent = (int)((double)ratingSummary.Rating2StarCount / ratingSummary.RatingCount * 100); + ratingSummaryBasicViewModel.Rating3StarPercent = (int)((double)ratingSummary.Rating3StarCount / ratingSummary.RatingCount * 100); + ratingSummaryBasicViewModel.Rating4StarPercent = (int)((double)ratingSummary.Rating4StarCount / ratingSummary.RatingCount * 100); + ratingSummaryBasicViewModel.Rating5StarPercent = (int)((double)ratingSummary.Rating5StarCount / ratingSummary.RatingCount * 100); + + await cachingService.SetAsync(cacheKey, ratingSummaryBasicViewModel); + + return ratingSummaryBasicViewModel; + } + catch + { + throw; + } + } + } + + /// + /// Create a rating of an entity. + /// + /// The user Id. + /// The ratingViewModel. + /// The . + public async Task CreateRating(int userId, RatingViewModel ratingViewModel) + { + // Check that this is a valid rating. + var retVal = await ValidateRating(userId, ratingViewModel); + + // Check user has not already rated the resource. Only applies to create, not update. + if (await HasTheUserAlreadyRatedThisMajorVersion(userId, ratingViewModel.EntityVersionId)) + { + retVal.Add(new LearningHubValidationResult(false, "You have already rated this resource")); + retVal.IsValid = false; + } + + if (!retVal.IsValid) + { + return retVal; + } + + // All checks passed. Save a new rating... + await CreateNewRatingAndRecalcAverageAsync(userId, ratingViewModel); + + // Force cache refresh + string cacheKey = $"{CacheKeys.RatingSummaryBasic}:{ratingViewModel.EntityVersionId}"; + await cachingService.RemoveAsync(cacheKey); + + await GetRatingSummaryBasic(ratingViewModel.EntityVersionId); + + // TODO: Submit new resource record to findwise. + return retVal; + } + + /// + /// Update a rating of an entity. Can be performed on ratings for any minor version of a major version of the resource. + /// + /// The user Id. + /// The ratingViewModel. + /// The . + public async Task UpdateRating(int userId, RatingViewModel ratingViewModel) + { + // Check that this is a valid rating. + var retVal = await ValidateRating(userId, ratingViewModel); + if (!retVal.IsValid) + { + return retVal; + } + + // All checks passed. Mark old rating as deleted and create a new rating... + var oldRating = await resourceVersionRatingRepository.GetUsersPreviousRatingForSameMajorVersionAsync(ratingViewModel.EntityVersionId, userId); + if (oldRating == null) + { + retVal.Add(new LearningHubValidationResult(false, "A previous rating for this resource was not found")); + retVal.IsValid = false; + return retVal; + } + + oldRating.Deleted = true; + await resourceVersionRatingRepository.UpdateAsync(userId, oldRating); + + await CreateNewRatingAndRecalcAverageAsync(userId, ratingViewModel); + + // Force cache refresh + string cacheKey = $"{CacheKeys.RatingSummaryBasic}:{ratingViewModel.EntityVersionId}"; + await cachingService.RemoveAsync(cacheKey); + + await GetRatingSummaryBasic(ratingViewModel.EntityVersionId); + + return retVal; + } + + private async Task ValidateRating(int userId, RatingViewModel ratingViewModel) + { + // Basic validation + var ratingValidator = new RatingValidator(); + var validationResults = await ratingValidator.ValidateAsync(ratingViewModel); + var retVal = new LearningHubValidationResult(validationResults); + + // Check that the resource version is the most recent version. Can't rate old versions. Not possible through UI but check anyway. + if (!await IsTheCurrentVersion(ratingViewModel.EntityVersionId)) + { + retVal.Add(new LearningHubValidationResult(false, "This version of the resource is not the current version and cannot be rated")); + retVal.IsValid = false; + } + + // Check the user is not the contributor. Contributor can't rate own resources. + if (await IsTheUserTheContributor(userId, ratingViewModel.EntityVersionId)) + { + retVal.Add(new LearningHubValidationResult(false, "A contributor cannot rate their own resources")); + retVal.IsValid = false; + } + + // Check the user has performed the activity. + if (!await HasUserCompletedActivity(userId, ratingViewModel.EntityVersionId)) + { + retVal.Add(new LearningHubValidationResult(false, "You have to access the resource before you can rate it")); + retVal.IsValid = false; + } + + return retVal; + } + + private async Task CreateNewRatingAndRecalcAverageAsync(int userId, RatingViewModel ratingViewModel) + { + var resourceVersionRating = new ResourceVersionRating() + { + UserId = userId, + Rating = ratingViewModel.Rating, + ResourceVersionId = ratingViewModel.EntityVersionId, + JobRoleId = ratingViewModel.JobRoleId.GetValueOrDefault(), + LocationId = ratingViewModel.LocationId, + }; + + await resourceVersionRatingRepository.CreateAsync(userId, resourceVersionRating); + + // Recalculate the resource's star rating. + var ratingSummary = await resourceVersionRatingSummaryRepository.GetByResourceVersionIdAsync(ratingViewModel.EntityVersionId); + + var allRatings = await resourceVersionRatingRepository.GetRatingCountsForResourceVersionAsync(ratingViewModel.EntityVersionId); + + ratingSummary.Rating1StarCount = allRatings[0]; + ratingSummary.Rating2StarCount = allRatings[1]; + ratingSummary.Rating3StarCount = allRatings[2]; + ratingSummary.Rating4StarCount = allRatings[3]; + ratingSummary.Rating5StarCount = allRatings[4]; + ratingSummary.RatingCount = allRatings.Sum(); + ratingSummary.AverageRating = decimal.Round((decimal)(allRatings[0] * 1 + allRatings[1] * 2 + allRatings[2] * 3 + allRatings[3] * 4 + allRatings[4] * 5) / ratingSummary.RatingCount, 1); + + await resourceVersionRatingSummaryRepository.UpdateAsync(userId, ratingSummary); + + // Submit updated rating to Findwise. + await this.resourceService.SubmitResourceVersionToSearchAsync(ratingViewModel.EntityVersionId, userId); + } + + private async Task IsTheCurrentVersion(int entityVersionId) + { + bool isCurrentVersion = await this.resourceRepository.IsCurrentVersionAsync(entityVersionId); + return isCurrentVersion; + } + + private async Task IsTheUserTheContributor(int userId, int entityVersionId) + { + var publication = await publicationRepository.GetByResourceVersionIdAsync(entityVersionId); + return publication.CreateUserId == userId; + } + + private async Task HasTheUserAlreadyRatedThisMajorVersion(int userId, int entityVersionId) + { + var rating = await resourceVersionRatingRepository.GetUsersPreviousRatingForSameMajorVersionAsync(entityVersionId, userId); + return rating != null; + } + + private async Task HasUserCompletedActivity(int userId, int entityVersionId) + { + var hasCompleted = await resourceVersionRepository.HasUserCompletedActivity(userId, entityVersionId); + return hasCompleted; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs index 179e75922..d03732124 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceService.cs @@ -100,6 +100,7 @@ public class ResourceService : IResourceService private readonly IResourceVersionProviderRepository resourceVersionProviderRepository; private readonly IResourceVersionAuthorRepository resourceVersionAuthorRepository; private readonly IFileChunkDetailRepository fileChunkDetailRepository; + private readonly IResourceSyncService resourceSyncService; /// /// Initializes a new instance of the class. @@ -138,6 +139,8 @@ public class ResourceService : IResourceService /// /// The . /// + /// + /// /// /// /// @@ -160,7 +163,7 @@ public class ResourceService : IResourceService /// /// /// - public ResourceService(ILearningHubService learningHubService, IInternalSystemService internalSystemService, IResourceVersionAuthorRepository resourceVersionAuthorRepository, IFileChunkDetailRepository fileChunkDetailRepository, IQueueCommunicatorService queueCommunicatorService, IResourceRepository resourceRepository, IResourceVersionProviderRepository resourceVersionProviderRepository, IProviderService providerService, IArticleResourceVersionFileRepository articleResourceVersionFileRepository, IPublicationRepository publicationRepository, IMigrationSourceRepository migrationSourceRepository, IQuestionBlockRepository questionBlockRepository, IVideoRepository videoRepository, IWholeSlideImageRepository wholeSlideImageRepository, IEmbeddedResourceVersionRepository embeddedResourceVersionRepository, IEquipmentResourceVersionRepository equipmentResourceVersionRepository, IImageResourceVersionRepository imageResourceVersionRepository, IBookmarkRepository bookmarkRepository, IAssessmentResourceActivityMatchQuestionRepository assessmentResourceActivityMatchQuestionRepository, IResourceVersionKeywordRepository resourceVersionKeywordRepository, IResourceVersionValidationResultRepository resourceVersionValidationResultRepository, ILogger logger, IWebLinkResourceVersionRepository webLinkResourceVersionRepository, ICaseResourceVersionRepository caseResourceVersionRepository, IScormResourceVersionRepository scormResourceVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, IResourceVersionRepository resourceVersionRepository, IHtmlResourceVersionRepository htmlResourceVersionRepository, IMapper mapper, IFileRepository fileRepository, IOptions azureConfig, IOptions learningHubConfig, IUserProfileService userProfileService, IResourceVersionFlagRepository resourceVersionFlagRepository, IArticleResourceVersionRepository articleResourceVersionRepository, IAudioResourceVersionRepository audioResourceVersionRepository, IVideoResourceVersionRepository videoResourceVersionRepository, IAssessmentResourceVersionRepository assessmentResourceVersionRepository, IResourceLicenceRepository resourceLicenceRepository, IResourceReferenceRepository resourceReferenceRepository, IResourceVersionUserAcceptanceRepository resourceVersionUserAcceptanceRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, ICachingService cachingService, ISearchService searchService, ICatalogueService catalogueService, INodeResourceRepository nodeResourceRepository, INodePathRepository nodePathRepository, IUserService userService, INodeRepository nodeRepository, LearningHubDbContext dbContext) + public ResourceService(ILearningHubService learningHubService, IFileTypeService fileTypeService, IBlockCollectionRepository blockCollectionRepository, IInternalSystemService internalSystemService, IResourceVersionAuthorRepository resourceVersionAuthorRepository, IFileChunkDetailRepository fileChunkDetailRepository, IQueueCommunicatorService queueCommunicatorService, IResourceRepository resourceRepository, IResourceVersionProviderRepository resourceVersionProviderRepository, IProviderService providerService, IArticleResourceVersionFileRepository articleResourceVersionFileRepository, IPublicationRepository publicationRepository, IMigrationSourceRepository migrationSourceRepository, IQuestionBlockRepository questionBlockRepository, IVideoRepository videoRepository, IWholeSlideImageRepository wholeSlideImageRepository, IEmbeddedResourceVersionRepository embeddedResourceVersionRepository, IEquipmentResourceVersionRepository equipmentResourceVersionRepository, IImageResourceVersionRepository imageResourceVersionRepository, IBookmarkRepository bookmarkRepository, IAssessmentResourceActivityMatchQuestionRepository assessmentResourceActivityMatchQuestionRepository, IResourceVersionKeywordRepository resourceVersionKeywordRepository, IResourceVersionValidationResultRepository resourceVersionValidationResultRepository, ILogger logger, IWebLinkResourceVersionRepository webLinkResourceVersionRepository, ICaseResourceVersionRepository caseResourceVersionRepository, IScormResourceVersionRepository scormResourceVersionRepository, IGenericFileResourceVersionRepository genericFileResourceVersionRepository, IResourceVersionRepository resourceVersionRepository, IHtmlResourceVersionRepository htmlResourceVersionRepository, IMapper mapper, IFileRepository fileRepository, IOptions azureConfig, IOptions learningHubConfig, IUserProfileService userProfileService, IResourceVersionFlagRepository resourceVersionFlagRepository, IArticleResourceVersionRepository articleResourceVersionRepository, IAudioResourceVersionRepository audioResourceVersionRepository, IVideoResourceVersionRepository videoResourceVersionRepository, IAssessmentResourceVersionRepository assessmentResourceVersionRepository, IResourceLicenceRepository resourceLicenceRepository, IResourceReferenceRepository resourceReferenceRepository, IResourceVersionUserAcceptanceRepository resourceVersionUserAcceptanceRepository, ICatalogueNodeVersionRepository catalogueNodeVersionRepository, ICachingService cachingService, ISearchService searchService, ICatalogueService catalogueService, INodeResourceRepository nodeResourceRepository, INodePathRepository nodePathRepository, IUserService userService, INodeRepository nodeRepository, IResourceSyncService resourceSyncService, LearningHubDbContext dbContext) { this.learningHubService = learningHubService; this.resourceRepository = resourceRepository; @@ -199,6 +202,7 @@ public ResourceService(ILearningHubService learningHubService, IInternalSystemSe this.providerService = providerService; this.nodePathRepository = nodePathRepository; this.nodeResourceRepository = nodeResourceRepository; + this.blockCollectionRepository = blockCollectionRepository; this.nodeRepository = nodeRepository; this.searchService = searchService; this.cachingService = cachingService; @@ -208,8 +212,15 @@ public ResourceService(ILearningHubService learningHubService, IInternalSystemSe this.migrationSourceRepository = migrationSourceRepository; this.queueCommunicatorService = queueCommunicatorService; this.internalSystemService = internalSystemService; + this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; + this.assessmentResourceVersionRepository = assessmentResourceVersionRepository; + this.wholeSlideImageRepository = wholeSlideImageRepository; + this.videoRepository = videoRepository; + this.fileTypeService = fileTypeService; + this.resourceSyncService = resourceSyncService; } + /// /// the get by id async. /// @@ -483,6 +494,7 @@ public async Task GetWebLinkDetailsByIdAsync(int resourceVersi public async Task GetCaseDetailsByIdAsync(int resourceVersionId) { CaseResourceVersion caseResourceVersion = await this.caseResourceVersionRepository.GetByResourceVersionIdAsync(resourceVersionId); + var number = caseResourceVersion?.BlockCollectionId; BlockCollection blockCollection = await this.blockCollectionRepository.GetBlockCollection(caseResourceVersion?.BlockCollectionId); BlockCollectionViewModel blockCollectionViewModel = null; @@ -2283,6 +2295,20 @@ public async Task SubmitResourceVersionForPublish(P return new LearningHubValidationResult(true); } + /// + /// Submits a published resource version to the Findwise search. + /// + /// The resourceVersionId. + /// The userId. + /// The . + public async Task<(bool success, int resourceReferenceId)> SubmitResourceVersionToSearchAsync(int resourceVersionId, int userId) + { + var searchResourceRequestModel = await this.resourceSyncService.BuildSearchResourceRequestModel(resourceVersionId); + bool success = await this.searchService.SendResourceForSearchAsync(searchResourceRequestModel, userId, 3); + + return (success, searchResourceRequestModel.ResourceReferenceId); + } + /// /// The update resource version async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs new file mode 100644 index 000000000..ba6e4b243 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/ResourceSyncService.cs @@ -0,0 +1,287 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using LearningHub.Nhs.Models.Entities.Resource; + using LearningHub.Nhs.Models.Resource; + using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Helpers; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Hierarchy; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories.Resources; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + + /// + /// The resource sync service. + /// + public class ResourceSyncService : IResourceSyncService + { + private readonly IResourceSyncRepository resourceSyncRepository; + private readonly IResourceVersionRepository resourceVersionRepository; + private readonly ICatalogueNodeVersionRepository catalogueNodeVersionRepository; + private readonly IGenericFileResourceVersionRepository genericFileResourceVersionRepository; + private readonly IFindwiseApiFacade findwiseApiFacade; + private readonly IMapper mapper; + + /// + /// Initializes a new instance of the class. + /// + /// The resourceSyncRepository. + /// The resourceVersionRepository. + /// The catalogueNodeVersionRepository. + /// The genericFileResourceVersionRepository. + /// The findwiseApiFacade. + /// The mapper. + public ResourceSyncService( + IResourceSyncRepository resourceSyncRepository, + IResourceVersionRepository resourceVersionRepository, + ICatalogueNodeVersionRepository catalogueNodeVersionRepository, + IGenericFileResourceVersionRepository genericFileResourceVersionRepository, + IFindwiseApiFacade findwiseApiFacade, + IMapper mapper) + { + this.resourceSyncRepository = resourceSyncRepository; + this.resourceVersionRepository = resourceVersionRepository; + this.catalogueNodeVersionRepository = catalogueNodeVersionRepository; + this.genericFileResourceVersionRepository = genericFileResourceVersionRepository; + this.findwiseApiFacade = findwiseApiFacade; + this.mapper = mapper; + } + + /// + /// The AddToSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + public async Task AddToSyncListAsync(int userId, List resourceIds) + { + await resourceSyncRepository.AddToSyncListAsync(userId, resourceIds); + } + + /// + /// The RemoveFromSyncListAsync. + /// + /// The userId. + /// The resourceIds. + /// The task. + public async Task RemoveFromSyncListAsync(int userId, List resourceIds) + { + await resourceSyncRepository.RemoveFromSyncListAsync(userId, resourceIds); + } + + /// + /// The GetSyncListForUser. + /// + /// The userId. + /// If the resource property should be populated. + /// The resource syncs. + public List GetSyncListForUser(int userId, bool includeResources) + { + return resourceSyncRepository.GetSyncListForUser(userId, includeResources).ToList(); + } + + /// + /// The GetSyncListResourcesForUser. + /// + /// The userId. + /// The list of resources. + public List GetSyncListResourcesForUser(int userId) + { + var list = GetSyncListForUser(userId, true).Select(x => x.Resource); + + return mapper.ProjectTo(list.AsQueryable()).ToList(); + } + + /// + /// The SyncForUserAsync. + /// + /// The userId. + /// The task. + public async Task SyncForUserAsync(int userId) + { + // Obtain Resource Version dataset related to the sync list for the supplied User Id. + var syncList = resourceSyncRepository.GetSyncListForUser(userId, true).ToList(); + + var resources = syncList.Select(x => x.Resource).ToList(); + + // Sync Updates + var resourcesToUpdate = resources.Where(x => !this.ResourceIsUnpublished(x)).ToList(); + var resourceVersionIdList = resourcesToUpdate.Select(x => x.Id).ToList(); + var mappedResourcesToUpdate = await this.BuildSearchResourceRequestModelList(resourceVersionIdList); + + // Validate Findwise submission + var invalidResourceDetails = new List(); + foreach (var r in mappedResourcesToUpdate) + { + if (!r.IsValidForSubmission()) + { + invalidResourceDetails.Add(r.Title); + } + } + + if (invalidResourceDetails.Count > 0) + { + return new LearningHubValidationResult(false, $"The following Resources are not valid for submission: {string.Join(", ", invalidResourceDetails)}"); + } + + // Send update to Findwise + if (mappedResourcesToUpdate.Any()) + { + await findwiseApiFacade.AddOrReplaceAsync(mappedResourcesToUpdate.ToList()); + } + + // Synce Deletes + var resourcesToDelete = resources.Where(x => this.ResourceIsUnpublished(x)).ToList(); + var mappedResourcesToDelete = resourcesToDelete.Select(x => mapper.Map(x)).ToList(); + + if (mappedResourcesToDelete.Any()) + { + await findwiseApiFacade.RemoveAsync(mappedResourcesToDelete.ToList()); + } + + await resourceSyncRepository.SetSyncedForUserAsync(userId); + + return new LearningHubValidationResult(true); + } + + /// + /// The BuildSearchResourceRequestModelList. + /// 04-11-20 KD TODO / Note: + /// This services a temporary mechanism to build the correct data set for Findwise. + /// This should be refactored to potentially use stored proc for this data retrieval - e.g. using common View/s. + /// Refactor bulk Findwise Sync operation to Azure Function Activity. + /// this method processes a Resource Version dataset to only retain active Resource Reference and Catalogue information. + /// Supplements formatted "authored date string" for Generic file resource type. + /// Note - require "IsActive" indication on the NodePath ongoing - only publish Resource Reference with an active node path. + /// (i.e. var activeNodePaths = activeNodeResources.Select(s => s.Node.NodePaths.Where(np => np.IsActive));) + /// Ongoing - may not wish to return Resource Reference from Findwise - currently only holds one Resource Reference but there may be multiple. + /// + /// The resource version id list. + /// The task. + public async Task> BuildSearchResourceRequestModelList(List resourceVersionIds) + { + var resourceVersions = await resourceVersionRepository.GetResourceVersionsForSearchSubmission(resourceVersionIds); + var catalogues = await catalogueNodeVersionRepository.GetPublishedCatalogues(); + + var resourceCatalogueDictionary = new Dictionary>(); + var resourceReferenceDictionary = new Dictionary>(); + var resourceAuthoredDateDictionary = new Dictionary(); + + foreach (var rv in resourceVersions) + { + var activeResourceReferences = new List(); + var activeNodeResources = rv.Resource.NodeResource.Where(nr => nr.VersionStatusEnum == Nhs.Models.Enums.VersionStatusEnum.Published); + var activeNodePaths = activeNodeResources.SelectMany(s => s.Node.NodePaths.Where(np => np.IsActive)); + var activeCatalogues = activeNodePaths.Select(s => s.CatalogueNodeId).Distinct().ToList(); + + foreach (var rr in rv.Resource.ResourceReference) + { + if (activeNodePaths.Any(np => np.Id == rr.NodePathId)) + { + activeResourceReferences.Add(rr); + } + } + + // Record additional data for submission to Findwise + resourceReferenceDictionary.Add(rv.Resource.Id, activeResourceReferences.Select(s => s.OriginalResourceReferenceId).Distinct().ToList()); + resourceCatalogueDictionary.Add(rv.Resource.Id, activeCatalogues); + + if (rv.Resource.ResourceTypeEnum == LearningHub.Nhs.Models.Enums.ResourceTypeEnum.GenericFile) + { + var genericFileResourceVersion = await genericFileResourceVersionRepository.GetByResourceVersionIdAsync(rv.Id); + resourceAuthoredDateDictionary.Add(rv.Resource.Id, TextHelper.CombineDateComponents(genericFileResourceVersion.AuthoredYear, genericFileResourceVersion.AuthoredMonth, genericFileResourceVersion.AuthoredDayOfMonth)); + } + } + + // Automapper for basic properties + var searchResourceRequestModels = resourceVersions.Select(x => mapper.Map(x)).ToList(); + + // Append extra resource reference, catalogue info, formatted author date string. + // Findwise currently only supports one resource reference. + foreach (var r in searchResourceRequestModels) + { + int resourceId = int.Parse(r.Id); + List resourceReferenceIds = new List(); + resourceReferenceDictionary.TryGetValue(resourceId, out resourceReferenceIds); + r.ResourceReferenceId = resourceReferenceDictionary[resourceId].FirstOrDefault(); + + List catalogueIds = new List(); + resourceCatalogueDictionary.TryGetValue(resourceId, out catalogueIds); + r.CatalogueIds = catalogueIds; + r.LocationPaths = catalogues.Where(c => r.CatalogueIds.Contains(c.NodeVersion.NodeId)).Select(c => c.Name).ToList(); + + // Apply formatted authored date string if applicable. + string authoredDateString = string.Empty; + resourceAuthoredDateDictionary.TryGetValue(resourceId, out authoredDateString); + r.AuthoredDateString = authoredDateString; + } + + return searchResourceRequestModels; + } + + /// + /// The BuildSearchResourceRequestModel. + /// + /// The resource version id. + /// The task. + public async Task BuildSearchResourceRequestModel(int resourceVersionId) + { + var resourceVersionIds = new List() { resourceVersionId }; + var searchResourceRequestModels = await BuildSearchResourceRequestModelList(resourceVersionIds); + return searchResourceRequestModels.First(); + } + + /// + /// The sync single async. + /// + /// The resourceVersionId. + /// The task. + public async Task SyncSingleAsync(int resourceVersionId) + { + var resourceVersion = GetResourcesWithIncludes().SingleOrDefault(x => x.Id == resourceVersionId); + if (resourceVersion == null) + { + throw new Exception($"No resource version found for id '{resourceVersionId}'"); + } + + var searchResourceRequestModel = await BuildSearchResourceRequestModel(resourceVersionId); + if (ResourceIsUnpublished(resourceVersion)) + { + await findwiseApiFacade.RemoveAsync(new List { searchResourceRequestModel }); + } + else + { + if (!searchResourceRequestModel.IsValidForSubmission()) + { + return new LearningHubValidationResult(false, $"Resource '{resourceVersion.Title}' is not valid for submission to Findwise"); + } + + await findwiseApiFacade.AddOrReplaceAsync(new List { searchResourceRequestModel }); + } + + return new LearningHubValidationResult(true); + } + + private bool ResourceIsUnpublished(ResourceVersion r) + { + return r.VersionStatusEnum == Nhs.Models.Enums.VersionStatusEnum.Unpublished + || r.VersionStatusEnum == Nhs.Models.Enums.VersionStatusEnum.Draft; + } + + private IQueryable GetResourcesWithIncludes() + { + return resourceVersionRepository.GetAll() + .Include(x => x.CreateUser) + .Include(x => x.Resource).ThenInclude(x => x.ResourceReference) + .Include(x => x.ResourceVersionKeyword) + .Include(x => x.ResourceVersionAuthor) + .Include(x => x.ResourceVersionRatingSummary) + .Include(x => x.Publication); + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs index 09ecc7ac2..09a0fb3d0 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/SearchService.cs @@ -3,6 +3,8 @@ namespace LearningHub.Nhs.OpenApi.Services.Services using System; using System.Collections.Generic; using System.Linq; + using System.Net.Http; + using System.Text; using System.Threading.Tasks; using System.Web; using LearningHub.Nhs.Models.Entities.Activity; @@ -120,6 +122,62 @@ public async Task RemoveResourceFromSearchAsync(int resourceId) } } + /// + /// The send resource for search Async method. + /// + /// The resource to be added to search. + /// The user id. + /// number of iterations. + /// The . + public async Task SendResourceForSearchAsync(SearchResourceRequestModel searchResourceRequestModel, int userId, int? iterations) + { + try + { + if (string.IsNullOrEmpty(this.findwiseConfig.IndexMethod)) + { + this.logger.LogWarning("The FindWiseIndexMethod is not configured. Resource not added to search results"); + } + else + { + List resourceList =[searchResourceRequestModel]; + + var json = JsonConvert.SerializeObject(resourceList, new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" }); + + var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); + var client = await this.findwiseClient.GetClient(this.findwiseConfig.IndexUrl); + + var request = string.Format(this.findwiseConfig.SearchBaseUrl, this.findwiseConfig.CollectionIds.Resource) + $"?token={this.findwiseConfig.Token}"; + var response = await client.PostAsync(request, stringContent).ConfigureAwait(false); + iterations--; + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); + throw new Exception("AccessDenied"); + } + else if (!response.IsSuccessStatusCode) + { + if (iterations < 0) + { + this.logger.LogError("Save to FindWise failed for resourceId:" + searchResourceRequestModel.Id.ToString() + "HTTP Status Code:" + response.StatusCode.ToString()); + throw new Exception("Posting of resource to search failed: " + stringContent); + } + + await this.SendResourceForSearchAsync(searchResourceRequestModel, userId, iterations); + } + else + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + throw new Exception("Posting of resource to search failed: " + searchResourceRequestModel.Id + " : " + ex.Message); + } + } + /// /// Gets AllCatalogue search results from findwise api call. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserNotificationService.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserNotificationService.cs new file mode 100644 index 000000000..0bf034ad9 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Services/UserNotificationService.cs @@ -0,0 +1,162 @@ +namespace LearningHub.Nhs.OpenApi.Services.Services +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Notification; + using LearningHub.Nhs.Models.Validation; + using LearningHub.Nhs.OpenApi.Repositories.Interface.Repositories; + using LearningHub.Nhs.OpenApi.Services.Interface.Services; + using Microsoft.EntityFrameworkCore; + + /// + /// The user notification service. + /// + public class UserNotificationService : IUserNotificationService + { + private readonly IUserNotificationRepository usernotificationRepository; + private readonly ITimezoneOffsetManager timezoneOffsetManager; + + /// + /// Initializes a new instance of the class. + /// + /// The usernotification repository. + /// The timezoneOffsetManager. + public UserNotificationService(IUserNotificationRepository usernotificationRepository, ITimezoneOffsetManager timezoneOffsetManager) + { + this.usernotificationRepository = usernotificationRepository; + this.timezoneOffsetManager = timezoneOffsetManager; + } + + /// + /// The get by id async. + /// + /// The id. + /// The . + public async Task GetByIdAsync(int id) + { + return await usernotificationRepository.GetByIdAsync(id); + } + + /// + /// The get by id async. + /// + /// The notificationId. + /// The userId. + /// The . + public async Task GetByIdAndUserIdAsync(int notificationId, int userId) + { + return await usernotificationRepository.GetByNotificationAndUserIdAsync(userId, notificationId); + } + + /// + /// The get user unread notification count async. + /// + /// The userid. + /// The . + public async Task GetUserUnreadNotificationCountAsync(int userid) + { + return await usernotificationRepository.GetUserUnreadNotificationCountAsync(userid); + } + + /// + /// The update async. + /// + /// The user id. + /// The notification. + /// The . + public async Task UpdateAsync(int userId, UserNotification notification) + { + notification.Notification = null; + notification.User = null; + notification.ReadOnDate = notification.ReadOnDate.HasValue ? timezoneOffsetManager.ConvertToUserTimezone(notification.ReadOnDate.Value) : null; + await usernotificationRepository.UpdateAsync(userId, notification); + return new LearningHubValidationResult { IsValid = true }; + } + + /// + /// The create async. + /// + /// The user id. + /// The notification. + /// The . + public async Task CreateAsync(int userId, UserNotification notification) + { + var notificationUserId = notification.UserId == 0 + ? userId + : notification.UserId; + var result = new LearningHubValidationResult(); + var currentNotification = await usernotificationRepository.GetByNotificationAndUserIdAsync(notificationUserId, notification.NotificationId); + if (currentNotification != null) + { + result.IsValid = false; + return result; + } + + notification.UserId = notificationUserId; + await usernotificationRepository.CreateAsync(userId, notification); + result.IsValid = true; + return result; + } + + /// + /// The get page async. + /// + /// The user id. + /// Notification priority type. + /// The page. + /// The page size. + /// The sort column. + /// The sort direction. + /// The . + public async Task> GetPageAsync( + int userId, NotificationPriorityEnum priorityType, int page, int pageSize, string sortColumn = "", string sortDirection = "") + { + PagedResultSet result = new PagedResultSet(); + + // Get existing user notifications + var items = usernotificationRepository.GetAllNonDismissed(userId, priorityType, sortColumn, sortDirection); + + //// Join and map + var viewmodelitems = await items.Select(n => MapNotificationToDummyUserNotification(n)).ToListAsync(); + result.TotalItemCount = viewmodelitems.Count(); + + int totalPages = (int)Math.Ceiling((double)result.TotalItemCount / pageSize); + + if (totalPages < page) + { + page = totalPages; + } + + var pageditems = viewmodelitems.Skip((page - 1) * pageSize).Take(pageSize).ToList(); + result.Items = pageditems; + + return result; + } + + /// + /// The map notification to dummy user notification. + /// + /// The n. + /// The . + private static UserNotificationViewModel MapNotificationToDummyUserNotification(UserSpecificNotification n) + { + var userNotification = new UserNotificationViewModel() + { + Body = n.Notification.Message, + Title = n.Notification.Title, + NotificationId = n.Notification.Id, + Date = n.Notification.StartDate, + UserDismissable = n.Notification.UserDismissable, + ReadOnDate = n.UserNotification?.ReadOnDate, + Id = n.UserNotification != null ? n.UserNotification.Id : -1, + NotificationType = n.Notification.NotificationTypeEnum, + NotificationPriority = n.Notification.NotificationPriorityEnum, + }; + return userNotification; + } + } +} \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs index 03b843a1a..f620c101b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/Startup.cs @@ -28,10 +28,15 @@ public static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient(); diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs index 4d50e8b82..cfda30045 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/CatalogueServiceTests.cs @@ -69,7 +69,7 @@ public CatalogueServiceTests() this.findwiseConfig = new Mock>(); this.notificationSenderService = new Mock(); this.timezoneOffsetManager = new Mock(); - this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.nodeRepository.Object, this.userUserGroupRepository.Object, this.mapper.Object, this.findwiseConfig.Object, this.learningHubConfig.Object, this.catalogueNodeVersionRepository.Object, this.nodeResourceRepository.Object, this.resourceVersionRepository.Object, this.roleUserGroupRepository.Object, this.providerService.Object, this.catalogueAccessRequestRepository.Object, this.userRepository.Object, this.userProfileRepository.Object, this.emailSenderService.Object, this.bookmarkRepository.Object, this.nodeActivityRepository.Object, this.findwiseApiFacade.Object); + this.catalogueService = new CatalogueService(this.catalogueRepository.Object, this.nodeRepository.Object, this.userUserGroupRepository.Object, this.mapper.Object, this.findwiseConfig.Object, this.learningHubConfig.Object, this.catalogueNodeVersionRepository.Object, this.nodeResourceRepository.Object, this.resourceVersionRepository.Object, this.roleUserGroupRepository.Object, this.providerService.Object, this.catalogueAccessRequestRepository.Object, this.userRepository.Object, this.userProfileRepository.Object, this.emailSenderService.Object, this.bookmarkRepository.Object, this.nodeActivityRepository.Object, this.findwiseApiFacade.Object,this.notificationSenderService.Object,this.timezoneOffsetManager.Object); } private static IEnumerable CatalogueNodeVersionList => new List() diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs index f59c2153c..396ad050d 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Tests/Services/Services/ResourceServiceTests.cs @@ -33,6 +33,7 @@ public class ResourceServiceTests { private readonly Mock learningHubService; private readonly ResourceService resourceService; + private readonly Mock resourceSyncService; private readonly Mock resourceRepository; private readonly Mock> logger; private readonly Mock mapper; @@ -44,47 +45,46 @@ public class ResourceServiceTests private readonly Mock scormResourceVersionRepository; private readonly Mock genericFileResourceVersionRepository; private readonly Mock resourceVersionRepository; - private readonly Mock assessmentResourceActivityMatchQuestionRepository; - private readonly Mock htmlResourceVersionRepository; - private readonly Mock articleResourceVersionRepository; + private readonly Mock assessmentResourceActivityMatchQuestionRepository; + private readonly Mock htmlResourceVersionRepository; + private readonly Mock articleResourceVersionRepository; private readonly Mock articleResourceVersionFileRepository; private readonly Mock audioResourceVersionRepository; private readonly Mock bookmarkRepository; - private readonly Mock imageResourceVersionRepository; - private readonly Mock videoResourceVersionRepository; - private readonly Mock assessmentResourceVersionRepository; - private readonly Mock resourceLicenceRepository; - private readonly Mock resourceReferenceRepository; - private readonly Mock resourceVersionUserAcceptanceRepository; - private readonly Mock resourceVersionFlagRepository; - private readonly Mock resourceVersionValidationResultRepository; - private readonly Mock resourceVersionKeywordRepository; - private readonly Mock catalogueNodeVersionRepository; - private readonly Mock embeddedResourceVersionRepository; - private readonly Mock videoRepository; - private readonly Mock wholeSlideImageRepository; - private readonly Mock nodePathRepository; - private readonly Mock nodeResourceRepository; - private readonly Mock equipmentResourceVersionRepository; - private readonly Mock publicationRepository; - private readonly Mock questionBlockRepository; - private readonly Mock migrationSourceRepository; + private readonly Mock imageResourceVersionRepository; + private readonly Mock videoResourceVersionRepository; + private readonly Mock assessmentResourceVersionRepository; + private readonly Mock resourceLicenceRepository; + private readonly Mock resourceReferenceRepository; + private readonly Mock resourceVersionUserAcceptanceRepository; + private readonly Mock resourceVersionFlagRepository; + private readonly Mock resourceVersionValidationResultRepository; + private readonly Mock resourceVersionKeywordRepository; + private readonly Mock catalogueNodeVersionRepository; + private readonly Mock embeddedResourceVersionRepository; + private readonly Mock videoRepository; + private readonly Mock wholeSlideImageRepository; + private readonly Mock nodePathRepository; + private readonly Mock nodeResourceRepository; + private readonly Mock equipmentResourceVersionRepository; + private readonly Mock publicationRepository; + private readonly Mock questionBlockRepository; + private readonly Mock migrationSourceRepository; private readonly Mock dbContext; - // private readonly Mock dbContext; private readonly Mock nodeRepository; - private readonly Mock fileRepository; + private readonly Mock fileRepository; private readonly Mock> azureConfig; private readonly Mock> learningHubConfig; - private readonly Mock cachingService; - private readonly Mock searchService; - private readonly Mock catalogueService; - private readonly Mock userService; - private readonly Mock providerService; - private readonly Mock internalSystemService; - private readonly Mock queueCommunicatorService; - private readonly Mock resourceVersionProviderRepository; - private readonly Mock resourceVersionAuthorRepository; - private readonly Mock fileChunkDetailRepository; + private readonly Mock cachingService; + private readonly Mock searchService; + private readonly Mock catalogueService; + private readonly Mock userService; + private readonly Mock providerService; + private readonly Mock internalSystemService; + private readonly Mock queueCommunicatorService; + private readonly Mock resourceVersionProviderRepository; + private readonly Mock resourceVersionAuthorRepository; + private readonly Mock fileChunkDetailRepository; private readonly int currentUserId; public ResourceServiceTests() @@ -93,10 +93,11 @@ public ResourceServiceTests() this.currentUserId = 57541; this.learningHubService = new Mock(); + this.resourceSyncService = new Mock(); this.resourceRepository = new Mock(); this.fileRepository = new Mock(); this.articleResourceVersionFileRepository = new Mock(); - this.articleResourceVersionRepository= new Mock(); + this.articleResourceVersionRepository = new Mock(); this.mapper = new Mock(); this.fileTypeService = new Mock(); this.userProfileService = new Mock(); @@ -144,7 +145,7 @@ public ResourceServiceTests() this.resourceVersionAuthorRepository = new Mock(); this.fileChunkDetailRepository = new Mock(); this.logger = new Mock>(); - this.resourceService = new ResourceService(this.learningHubService.Object, this.internalSystemService.Object, this.resourceVersionAuthorRepository.Object, this.fileChunkDetailRepository.Object, this.queueCommunicatorService.Object, this.resourceRepository.Object, this.resourceVersionProviderRepository.Object, this.providerService.Object, this.articleResourceVersionFileRepository.Object, this.publicationRepository.Object, this.migrationSourceRepository.Object, this.questionBlockRepository.Object, this.videoRepository.Object, this.wholeSlideImageRepository.Object, this.embeddedResourceVersionRepository.Object, this.equipmentResourceVersionRepository.Object, this.imageResourceVersionRepository.Object, this.bookmarkRepository.Object, this.assessmentResourceActivityMatchQuestionRepository.Object, this.resourceVersionKeywordRepository.Object, this.resourceVersionValidationResultRepository.Object, this.logger.Object, this.webLinkResourceVersionRepository.Object, this.caseResourceVersionRepository.Object, this.scormResourceVersionRepository.Object, this.genericFileResourceVersionRepository.Object, this.resourceVersionRepository.Object, this.htmlResourceVersionRepository.Object,this.mapper.Object, this.fileRepository.Object, this.azureConfig.Object, this.learningHubConfig.Object, this.userProfileService.Object, this.resourceVersionFlagRepository.Object, this.articleResourceVersionRepository.Object, this.audioResourceVersionRepository.Object, this.videoResourceVersionRepository.Object, this.assessmentResourceVersionRepository.Object, this.resourceLicenceRepository.Object, this.resourceReferenceRepository.Object, this.resourceVersionUserAcceptanceRepository.Object, this.catalogueNodeVersionRepository.Object, this.cachingService.Object, this.searchService.Object, this.catalogueService.Object, this.nodeResourceRepository.Object, this.nodePathRepository.Object, this.userService.Object, this.nodeRepository.Object, this.dbContext.Object.As()); + this.resourceService = new ResourceService(this.learningHubService.Object, this.fileTypeService.Object, this.blockCollectionRepository.Object, this.internalSystemService.Object, this.resourceVersionAuthorRepository.Object, this.fileChunkDetailRepository.Object, this.queueCommunicatorService.Object, this.resourceRepository.Object, this.resourceVersionProviderRepository.Object, this.providerService.Object, this.articleResourceVersionFileRepository.Object, this.publicationRepository.Object, this.migrationSourceRepository.Object, this.questionBlockRepository.Object, this.videoRepository.Object, this.wholeSlideImageRepository.Object, this.embeddedResourceVersionRepository.Object, this.equipmentResourceVersionRepository.Object, this.imageResourceVersionRepository.Object, this.bookmarkRepository.Object, this.assessmentResourceActivityMatchQuestionRepository.Object, this.resourceVersionKeywordRepository.Object, this.resourceVersionValidationResultRepository.Object, this.logger.Object, this.webLinkResourceVersionRepository.Object, this.caseResourceVersionRepository.Object, this.scormResourceVersionRepository.Object, this.genericFileResourceVersionRepository.Object, this.resourceVersionRepository.Object, this.htmlResourceVersionRepository.Object, this.mapper.Object, this.fileRepository.Object, this.azureConfig.Object, this.learningHubConfig.Object, this.userProfileService.Object, this.resourceVersionFlagRepository.Object, this.articleResourceVersionRepository.Object, this.audioResourceVersionRepository.Object, this.videoResourceVersionRepository.Object, this.assessmentResourceVersionRepository.Object, this.resourceLicenceRepository.Object, this.resourceReferenceRepository.Object, this.resourceVersionUserAcceptanceRepository.Object, this.catalogueNodeVersionRepository.Object, this.cachingService.Object, this.searchService.Object, this.catalogueService.Object, this.nodeResourceRepository.Object, this.nodePathRepository.Object, this.userService.Object, this.nodeRepository.Object,this.resourceSyncService.Object, this.dbContext.Object.As()); } private List ResourceActivityDTOList => new List() diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteHandler.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteHandler.cs new file mode 100644 index 000000000..a28bb1b6d --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteHandler.cs @@ -0,0 +1,43 @@ +namespace LearningHub.NHS.OpenAPI.Authentication +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Http; + + /// + /// Provide Authentication policy for Auth Service. + /// + public class ReadWriteHandler : AuthorizationHandler + { + /// + /// The context accessor. + /// + private readonly IHttpContextAccessor contextAccessor; + + /// + /// Initializes a new instance of the class. + /// Provide Authentication policy for Auth Service. + /// + /// The context Accessor. + public ReadWriteHandler(IHttpContextAccessor contextAccessor) + { + this.contextAccessor = contextAccessor; + } + + /// + /// Handle Authentication policy Requirement. + /// + /// The context. + /// The requirement. + /// A representing the asynchronous operation. + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReadWriteRequirement requirement) + { + if (requirement.HasReadWriteRole(context.User)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteRequirement.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteRequirement.cs new file mode 100644 index 000000000..9ffc6bbd4 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Authentication/ReadWriteRequirement.cs @@ -0,0 +1,47 @@ +namespace LearningHub.NHS.OpenAPI.Authentication +{ + using System.Collections.Generic; + using System.Security.Claims; + + using Microsoft.AspNetCore.Authorization; + + /// + /// Provide Authentication policy for Auth Service. + /// + public class ReadWriteRequirement : IAuthorizationRequirement + { + /// + /// Initializes a new instance of the class. + /// Provide Authentication policy for Auth Service. + /// + public ReadWriteRequirement() + { + } + + /// + /// The can read write. + /// + /// The user. + /// The . + public bool HasReadWriteRole(ClaimsPrincipal user) + { + bool retVal = false; + foreach (var role in ReadWriteRoles()) + { + if (user.IsInRole(role)) + { + retVal = true; + break; + } + } + + return retVal; + } + + /// + /// The read write roles. + /// + /// The Read Write Roles. + private List ReadWriteRoles() => new List() { "Administrator", "BlueUser" }; + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs index 1ffea0d56..7e42357d7 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/HierarchyController.cs @@ -61,6 +61,20 @@ public async Task> GetNodePathNodes(int nodePathId) return retVal; } + /// + /// 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 . + [HttpGet] + [Route("GetNodeContentsForCatalogueBrowse/{nodeId}/{includeEmptyFolder}")] + public async Task GetNodeContentsForCatalogueBrowse(int nodeId, bool includeEmptyFolder) + { + return this.Ok(await this.hierarchyService.GetNodeContentsForCatalogueBrowse(nodeId, includeEmptyFolder)); + } + /// /// 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. diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs index 242c5be57..e086f9eab 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/ResourceController.cs @@ -121,7 +121,7 @@ await this.searchService.Search( /// /// id. /// Resource data. - [HttpGet("GetResourceReferenceByOriginalId/{originalResourceReferenceId}")] + [HttpGet("{originalResourceReferenceId}")] public async Task GetResourceReferenceByOriginalId(int originalResourceReferenceId) { return await this.resourceService.GetResourceReferenceByOriginalId(originalResourceReferenceId, this.CurrentUserId.GetValueOrDefault()); @@ -215,7 +215,7 @@ public async Task> GetResour /// /// The id. /// The . - [HttpGet("{id}")] + [HttpGet("GetResource/{id}")] public async Task GetAsync(int id) { return this.Ok(await this.resourceService.GetResourceByIdAsync(id)); @@ -278,6 +278,7 @@ public async Task GetCaseResourceVersionAsync(int resourceVersionI { return this.Ok(await this.resourceService.GetCaseDetailsByIdAsync(resourceVersionId)); } + /// /// The GetFileStatusDetailsAsync. /// @@ -924,6 +925,8 @@ public async Task UpdateScormDetailAsync(ScormUpdateRequestViewMo } } + + /// /// Update HTML Detail. /// @@ -945,6 +948,17 @@ public async Task UpdateHtmlDetailAsync(HtmlResourceUpdateRequest } } + /// + /// Get specific ImageDetails by ResourceVersionId. + /// + /// The resourceVersionId. + /// The . + [HttpGet("GetImageDetails/{resourceVersionId}")] + public async Task GetImageDetailsAsync(int resourceVersionId) + { + return this.Ok(await this.resourceService.GetImageDetailsByIdAsync(resourceVersionId)); + } + /// /// The update image detail async. /// diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs index e2a3754c5..1fb149c79 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/UserController.cs @@ -1,14 +1,21 @@ namespace LearningHub.NHS.OpenAPI.Controllers { + using System.Collections.Generic; using System.Threading.Tasks; + using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Entities; + using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.Models.User; using LearningHub.NHS.OpenAPI.Helpers; + using LearningHub.Nhs.OpenApi.Models.Configuration; + using LearningHub.Nhs.OpenApi.Models.ViewModels; using LearningHub.Nhs.OpenApi.Services.Interface.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using System.Security.Claims; + using System.Security.Principal; /// /// The log controller. @@ -25,6 +32,10 @@ public class UserController : OpenApiControllerBase /// private readonly IUserProfileService userProfileService; private readonly IUserService userService; + private readonly ICacheService cacheService; + private readonly LearningHubConfig learningHubConfig; + private readonly IUserNotificationService userNotificationService; + private readonly INavigationPermissionService permissionService; /// /// Initializes a new instance of the class. @@ -35,18 +46,29 @@ public class UserController : OpenApiControllerBase /// /// The user profile service. /// - - /// The security service. - /// The logger. + /// + /// The securityService service. + /// + /// The userNotificationService. + /// The permissionService. + /// The cacheService. + /// The learningHubConfig. public UserController( IUserService userService, IUserProfileService userProfileService, ISecurityService securityService, - ILogger logger) + IUserNotificationService userNotificationService, + INavigationPermissionService permissionService, + ICacheService cacheService, + IOptions learningHubConfig) { this.userProfileService = userProfileService; this.securityService = securityService; this.userService = userService; + this.userNotificationService = userNotificationService; + this.permissionService = permissionService; + this.cacheService = cacheService; + this.learningHubConfig = learningHubConfig.Value; } /// @@ -98,7 +120,7 @@ public async Task> UpdateUserProfileAsync(UserProfile /// The . [HttpGet] [Route("GetByUserId/{id}")] - public async Task> GetByUserIdAsync(int id) + public async Task> GetByUserIdAsync(int id) { return this.Ok(await this.userService.GetByIdAsync(id)); } @@ -235,5 +257,89 @@ public async Task ValidateEmailChangeTokenAsync(string token, str return this.Ok(result); } + /// + /// GetLHUserNavigation. + /// + /// A representing the result of the asynchronous operation. + [HttpGet] + [AllowAnonymous] + [Route("GetLHUserNavigation")] + public async Task>> GetLHUserNavigation() + { + NavigationModel model; + + if (!this.User.Identity.IsAuthenticated) + { + model = this.permissionService.NotAuthenticated(); + } + else + { + var userId = this.User.Identity.GetCurrentUserId(); + + var (cacheExists, _) = await this.cacheService.TryGetAsync($"{userId}:LoginWizard"); + + model = await this.permissionService.GetNavigationModelAsync(this.User, !cacheExists, string.Empty); + + model.NotificationCount = await this.userNotificationService.GetUserUnreadNotificationCountAsync(userId); + } + + return this.MenuItems(model); + } + + + private List> MenuItems(NavigationModel model) + { + var menu = new List> + { + new Dictionary + { + { "title", "Browse catalogues" }, + { "url", this.learningHubConfig.BrowseCataloguesUrl }, + { "visible", model.ShowBrowseCatalogues }, + }, + new Dictionary + { + { "title", "My Contributions" }, + { "url", this.learningHubConfig.MyContributionsUrl }, + { "visible", model.ShowMyContributions }, + }, + new Dictionary + { + { "title", "My bookmarks" }, + { "url", this.learningHubConfig.MyBookmarksUrl }, + { "visible", model.ShowMyBookmarks }, + }, + new Dictionary + { + { "title", "Help" }, + { "url", this.learningHubConfig.HelpUrl }, + { "visible", model.ShowHelp }, + { "openInNewTab", true }, + }, + new Dictionary + { + { "title", "Notifications" }, + { "url", this.learningHubConfig.NotificationsUrl }, + { "visible", model.ShowNotifications }, + { "hasNotification", model.NotificationCount > 0 }, + { "notificationCount", model.NotificationCount }, + }, + new Dictionary + { + { "title", "Admin" }, + { "url", this.learningHubConfig.AdminUrl }, + { "visible", model.ShowAdmin }, + }, + new Dictionary + { + { "title", "Sign Out" }, + { "url", this.learningHubConfig.SignOutUrl }, + { "visible", model.ShowSignOut }, + }, + }; + return menu; + + } + } } \ No newline at end of file diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index 627401b12..533227ccd 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -4,9 +4,13 @@ namespace LearningHub.NHS.OpenAPI { + using System; using System.Collections.Generic; using System.IO; using AspNetCore.Authentication.ApiKey; + using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Extensions; using LearningHub.NHS.OpenAPI.Auth; using LearningHub.NHS.OpenAPI.Configuration; using LearningHub.NHS.OpenAPI.Middleware; @@ -25,11 +29,8 @@ namespace LearningHub.NHS.OpenAPI using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; - using LearningHub.Nhs.Caching; - using LearningHub.Nhs.Models.Enums; - using System.Configuration; - using System; - using LearningHub.Nhs.Models.Extensions; + using Microsoft.AspNetCore.Authorization; + using LearningHub.NHS.OpenAPI.Authentication; /// /// The Startup class. @@ -67,13 +68,14 @@ public void ConfigureServices(IServiceCollection services) options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "given_name", - RoleClaimType = "role", + RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", ValidateAudience = true, ValidAudiences = new List { "learninghubopenapi", "learninghubapi" }, }; }); services.AddCustomMiddleware(); + services.AddSingleton(); services.AddRepositories(this.Configuration); services.AddServices(); @@ -82,8 +84,16 @@ public void ConfigureServices(IServiceCollection services) options => options.UseSqlServer(this.Configuration.GetConnectionString("LearningHub"))); services.AddApplicationInsightsTelemetry(); - services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter())); - services.AddControllers(opt => { opt.Filters.Add(new AuthorizeFilter()); }); + services.AddControllers(options => + { + options.Filters.Add(new HttpResponseExceptionFilter()); + options.Filters.Add(new AuthorizeFilter()); + }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; + }); + services.AddSwaggerGen( c => { @@ -126,6 +136,7 @@ public void ConfigureServices(IServiceCollection services) Scopes = new Dictionary { { "learninghubapi", string.Empty }, + }, }, }, @@ -142,6 +153,13 @@ public void ConfigureServices(IServiceCollection services) }); }); + services.AddAuthorization(options => + { + options.AddPolicy( + "ReadWrite", + policy => policy.Requirements.Add(new ReadWriteRequirement())); + }); + var environment = this.Configuration.GetValue("Environment"); var envPrefix = environment.GetAbbreviation(); if (environment == EnvironmentEnum.Local) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index 0cde7cef1..741ead5fa 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -2393,6 +2393,37 @@ } } }, + "/Hierarchy/GetNodeContentsForCatalogueBrowse/{nodeId}/{includeEmptyFolder}": { + "get": { + "tags": [ + "Hierarchy" + ], + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "includeEmptyFolder", + "in": "path", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/Hierarchy/GetNodeContentsAdmin/{nodeId}/{readOnly}": { "get": { "tags": [ @@ -3207,7 +3238,7 @@ } } }, - "/Resource/GetResourceReferenceByOriginalId/{originalResourceReferenceId}": { + "/Resource/{resourceReferenceId}": { "get": { "tags": [ "Resource" @@ -3215,7 +3246,7 @@ "summary": "GET by Id.", "parameters": [ { - "name": "originalResourceReferenceId", + "name": "resourceReferenceId", "in": "path", "description": "id.", "required": true, @@ -3423,7 +3454,7 @@ } } }, - "/Resource/{id}": { + "/Resource/GetResource/{id}": { "get": { "tags": [ "Resource" @@ -5031,6 +5062,29 @@ } } }, + "/Resource/GetImageDetails/{resourceVersionId}": { + "get": { + "tags": [ + "Resource" + ], + "parameters": [ + { + "name": "resourceVersionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/Resource/UpdateImageDetail": { "post": { "tags": [ @@ -5997,21 +6051,18 @@ "tags": [ "User" ], - "summary": "Validate email change token.", "parameters": [ { "name": "token", "in": "path", - "description": "The token.", "required": true, "schema": { "type": "string" } }, { - "name": "loctoken", + "name": "locToken", "in": "path", - "description": "The loc Token.", "required": true, "schema": { "type": "string" @@ -6020,7 +6071,6 @@ { "name": "isUserRoleUpgrade", "in": "path", - "description": "isUserRoleUpgrade.", "required": true, "schema": { "type": "boolean" @@ -6033,6 +6083,47 @@ } } } + }, + "/User/GetLHUserNavigation": { + "get": { + "tags": [ + "User" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + } + } + } + } } }, "components": { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 92f23e650..804dc13f5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -45,19 +45,25 @@ "AzureBlobSettings": { "ConnectionString": "", "UploadContainer": "" - } + }, + "AzureStorageQueueConnectionString": "" }, "FindWise": { + "IndexUrl": "", "SearchBaseUrl": "", "CollectionIds": { "Resource": "hee-test", - "Catalogue": "", - "AutoSuggestion": "" + "Catalogue": "catalogues-test", + "AutoSuggestion": "auto-suggestion-test" }, "SearchEndpointPath": "rest/apps/HEE/searchers/", + "UrlClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click", + "UrlAutoSuggestionClickComponent": "rest/apps/HEE/searchers/hee/signals/hee/signal/click-hee", "Token": "", "DefaultItemLimitForSearch": 10, "IndexMethod": "/rest/v2/collections/{0}/documents", + "DescriptionLengthLimit": 3000, + "MaximumDescriptionLength": 150, "SpecialSearchCharacters": "#:&()/-[]%" }, "LearningHub": { @@ -70,7 +76,31 @@ "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", "UseRedisCache": true, "ResourcePublishQueueRouteName": "", - "HierarchyEditPublishQueueName": "" + "HierarchyEditPublishQueueName": "", + "Notifications": { + "PublishResourceTimeToProcessInSec": 30, + "ResourcePublishedTitle": "Published: ", + "ResourcePublished": "

The resource you contributed has been published.

", + "ResourcePublishFailedTitle": "Failed to Publish: ", + "ResourcePublishFailed": "

The resource you contributed failed to publish, which means that users cannot access it.

Please contact the support team for more information in order to fix this.

", + "ResourcePublishFailedWithReason": "

The resource you contributed failed to publish, which means that users cannot access it.

The error message generated was:
[ErrorMessage]

Please contact the support team for more information.

", + "ResourceAccessTitle": "What you can do in the Learning Hub has changed", + "ResourceReadonlyAccess": "

You can continue to search for and access resources in the Learning Hub, however you cannot contribute to it.

If you have any questions about this, please contact the support team.

", + "ResourceContributeAccess": "

You can now contribute to the Learning Hub and continue to search for and access resources.

If you have any questions about this, please contact the support team.

" + }, + "MyContributionsUrl": "/my-contributions", + "MyLearningUrl": "/MyLearning", + "MyBookmarksUrl": "/bookmark", + "SearchUrl": "", + "AdminUrl": "/admin", + "ForumsUrl": "", + "HelpUrl": "https://support.learninghub.nhs.uk/", + "MyRecordsUrl": "", + "NotificationsUrl": "/notification", + "RegisterUrl": "/register", + "SignOutUrl": "", + "MyAccountUrl": "/myaccount", + "BrowseCataloguesUrl": "/allcatalogue" }, "LearningHubAPIConfig": { "BaseUrl": "https://learninghub.nhs.uk/api" diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index bb0c6c6cc..4f9bb1aa6 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -60,5 +60,5 @@ - +