From d50f9a72c575b28b9240a6360811d852f2e928db Mon Sep 17 00:00:00 2001 From: EngincanV Date: Mon, 24 Aug 2020 14:21:09 +0300 Subject: [PATCH 01/69] Rating widget created --- .../CmsKitPublicWidgetsController.cs | 6 ++++ .../Shared/Components/Rating/Default.cshtml | 27 ++++++++++++++ .../Rating/RatingScriptBundleContributor.cs | 13 +++++++ .../Rating/RatingStyleBundleContributor.cs | 13 +++++++ .../Components/Rating/RatingViewComponent.cs | 20 +++++++++++ .../Shared/Components/Rating/default.css | 35 +++++++++++++++++++ .../Shared/Components/Rating/default.js | 1 + .../Volo.CmsKit.Public.Web.csproj | 4 +++ 8 files changed, 119 insertions(+) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs index c130ebefca..7cc9c410c4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting; +using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating; using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection; namespace Volo.CmsKit.Public.Web.Controllers @@ -16,5 +17,10 @@ namespace Volo.CmsKit.Public.Web.Controllers { return Task.FromResult((IActionResult)ViewComponent(typeof(CommentingViewComponent), new {entityType, entityId})); } + + public Task Rating() + { + return Task.FromResult((IActionResult) ViewComponent(typeof(RatingViewComponent))); + } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml new file mode 100644 index 0000000000..f9cd651ff1 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -0,0 +1,27 @@ +@using Volo.Abp.Users +@inject ICurrentUser CurrentUser + +@if (CurrentUser.IsAuthenticated) +{ +
+

My Simple Widget

+

This is a simple widget!

+
+} + +
+ + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs new file mode 100644 index 0000000000..a57ff8294f --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating +{ + public class RatingScriptBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Rating/default.js"); + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs new file mode 100644 index 0000000000..dc82f1f5da --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating +{ + public class RatingStyleBundleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/Pages/CmsKit/Shared/Components/Rating/default.css"); + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs new file mode 100644 index 0000000000..f77759ac20 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; + +namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating +{ + [ViewComponent(Name = "CmsRating")] + [Widget( + StyleTypes = new[] {typeof(RatingStyleBundleContributor)}, + ScriptTypes = new[] {typeof(RatingScriptBundleContributor)}, + RefreshUrl = "/CmsKitPublicWidgets/Rating" + )] + public class RatingViewComponent : AbpViewComponent + { + public IViewComponentResult Invoke() + { + return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml"); + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css new file mode 100644 index 0000000000..0dc4780ee9 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css @@ -0,0 +1,35 @@ +.rate { + float: left; + height: 46px; + padding: 0 10px; +} +.rate:not(:checked) > input { + position:absolute; + top:-9999px; +} +.rate:not(:checked) > label { + float:right; + width:1em; + overflow:hidden; + white-space:nowrap; + cursor:pointer; + font-size:30px; + color:#ccc; +} +.rate:not(:checked) > label:before { + content: '★ '; +} +.rate > input:checked ~ label { + color: #ffc700; +} +.rate:not(:checked) > label:hover, +.rate:not(:checked) > label:hover ~ label { + color: #deb217; +} +.rate > input:checked + label:hover, +.rate > input:checked + label:hover ~ label, +.rate > input:checked ~ label:hover, +.rate > input:checked ~ label:hover ~ label, +.rate > label:hover ~ input:checked ~ label { + color: #c59b08; +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj index 6d2f8ff303..3d52f544e0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj @@ -27,6 +27,10 @@ + + true + PreserveNewest + From 12011ffe03e12f0508c75c1a2122a22473894640 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Tue, 25 Aug 2020 11:39:43 +0300 Subject: [PATCH 02/69] CmsKit: Created CmsRating --- .../20200825083332_Rating_Added.Designer.cs | 1220 +++++++++ .../Migrations/20200825083332_Rating_Added.cs | 31 + .../UnifiedDbContextModelSnapshot.cs | 2414 +++++++++-------- .../GlobalFeatures/GlobalCmsKitFeatures.cs | 3 + .../CmsKit/GlobalFeatures/RatingsFeature.cs | 18 + .../Volo/CmsKit/Ratings/IRatingRepository.cs | 10 + .../Volo/CmsKit/Ratings/Rating.cs | 32 + .../CmsKitDbContextModelCreatingExtensions.cs | 13 + .../CmsKitEntityFrameworkCoreModule.cs | 2 + .../CmsKit/Ratings/EfCoreRatingRepository.cs | 14 + 10 files changed, 2561 insertions(+), 1196 deletions(-) create mode 100644 modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs create mode 100644 modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/RatingsFeature.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs new file mode 100644 index 0000000000..8a69c9cb89 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs @@ -0,0 +1,1220 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Migrations +{ + [DbContext(typeof(UnifiedDbContext))] + [Migration("20200825083332_Rating_Added")] + partial class Rating_Added + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationName") + .HasColumnName("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnName("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnName("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnName("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientName") + .HasColumnName("ClientName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Comments") + .HasColumnName("Comments") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnName("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Exceptions") + .HasColumnName("Exceptions") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("HttpMethod") + .HasColumnName("HttpMethod") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("HttpStatusCode") + .HasColumnName("HttpStatusCode") + .HasColumnType("int"); + + b.Property("ImpersonatorTenantId") + .HasColumnName("ImpersonatorTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("ImpersonatorUserId") + .HasColumnName("ImpersonatorUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnName("Url") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnName("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AbpAuditLogs"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnName("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasColumnName("MethodName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Parameters") + .HasColumnName("Parameters") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ServiceName") + .HasColumnName("ServiceName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AbpAuditLogActions"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangeTime") + .HasColumnName("ChangeTime") + .HasColumnType("datetime2"); + + b.Property("ChangeType") + .HasColumnName("ChangeType") + .HasColumnType("tinyint"); + + b.Property("EntityId") + .IsRequired() + .HasColumnName("EntityId") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("EntityTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasColumnName("EntityTypeFullName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("AbpEntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EntityChangeId") + .HasColumnType("uniqueidentifier"); + + b.Property("NewValue") + .HasColumnName("NewValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("OriginalValue") + .HasColumnName("OriginalValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("PropertyName") + .IsRequired() + .HasColumnName("PropertyName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasColumnName("PropertyTypeFullName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("AbpEntityPropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AbpTenants"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("TenantId", "Name"); + + b.ToTable("AbpTenantConnectionStrings"); + }); + + modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "RepliedCommentId"); + + b.HasIndex("TenantId", "EntityType", "EntityId"); + + b.ToTable("CmsComments"); + }); + + modelBuilder.Entity("Volo.CmsKit.Ratings.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("Star") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("CmsRatings"); + }); + + modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ReactionName") + .IsRequired() + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "EntityType", "EntityId", "ReactionName"); + + b.HasIndex("TenantId", "CreatorId", "EntityType", "EntityId", "ReactionName"); + + b.ToTable("CmsUserReactions"); + }); + + modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Email"); + + b.HasIndex("TenantId", "UserName"); + + b.ToTable("CmsUsers"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs new file mode 100644 index 0000000000..e6b7a3dfb3 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.CmsKit.Migrations +{ + public partial class Rating_Added : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CmsRatings", + columns: table => new + { + Id = table.Column(nullable: false), + Star = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CmsRatings", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CmsRatings"); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index 931ed2b852..0fd87d6cc2 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -1,1196 +1,1218 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.EntityFrameworkCore; -using Volo.CmsKit.EntityFrameworkCore; - -namespace Volo.CmsKit.Migrations -{ - [DbContext(typeof(UnifiedDbContext))] - partial class UnifiedDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.6") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ApplicationName") - .HasColumnName("ApplicationName") - .HasColumnType("nvarchar(96)") - .HasMaxLength(96); - - b.Property("BrowserInfo") - .HasColumnName("BrowserInfo") - .HasColumnType("nvarchar(512)") - .HasMaxLength(512); - - b.Property("ClientId") - .HasColumnName("ClientId") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ClientIpAddress") - .HasColumnName("ClientIpAddress") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ClientName") - .HasColumnName("ClientName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Comments") - .HasColumnName("Comments") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CorrelationId") - .HasColumnName("CorrelationId") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("Exceptions") - .HasColumnName("Exceptions") - .HasColumnType("nvarchar(4000)") - .HasMaxLength(4000); - - b.Property("ExecutionDuration") - .HasColumnName("ExecutionDuration") - .HasColumnType("int"); - - b.Property("ExecutionTime") - .HasColumnType("datetime2"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("HttpMethod") - .HasColumnName("HttpMethod") - .HasColumnType("nvarchar(16)") - .HasMaxLength(16); - - b.Property("HttpStatusCode") - .HasColumnName("HttpStatusCode") - .HasColumnType("int"); - - b.Property("ImpersonatorTenantId") - .HasColumnName("ImpersonatorTenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("ImpersonatorUserId") - .HasColumnName("ImpersonatorUserId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantName") - .HasColumnType("nvarchar(max)"); - - b.Property("Url") - .HasColumnName("Url") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("UserId") - .HasColumnName("UserId") - .HasColumnType("uniqueidentifier"); - - b.Property("UserName") - .HasColumnName("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "ExecutionTime"); - - b.HasIndex("TenantId", "UserId", "ExecutionTime"); - - b.ToTable("AbpAuditLogs"); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("AuditLogId") - .HasColumnName("AuditLogId") - .HasColumnType("uniqueidentifier"); - - b.Property("ExecutionDuration") - .HasColumnName("ExecutionDuration") - .HasColumnType("int"); - - b.Property("ExecutionTime") - .HasColumnName("ExecutionTime") - .HasColumnType("datetime2"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("MethodName") - .HasColumnName("MethodName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Parameters") - .HasColumnName("Parameters") - .HasColumnType("nvarchar(2000)") - .HasMaxLength(2000); - - b.Property("ServiceName") - .HasColumnName("ServiceName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("AuditLogId"); - - b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); - - b.ToTable("AbpAuditLogActions"); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("AuditLogId") - .HasColumnName("AuditLogId") - .HasColumnType("uniqueidentifier"); - - b.Property("ChangeTime") - .HasColumnName("ChangeTime") - .HasColumnType("datetime2"); - - b.Property("ChangeType") - .HasColumnName("ChangeType") - .HasColumnType("tinyint"); - - b.Property("EntityId") - .IsRequired() - .HasColumnName("EntityId") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("EntityTenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("EntityTypeFullName") - .IsRequired() - .HasColumnName("EntityTypeFullName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("AuditLogId"); - - b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); - - b.ToTable("AbpEntityChanges"); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("EntityChangeId") - .HasColumnType("uniqueidentifier"); - - b.Property("NewValue") - .HasColumnName("NewValue") - .HasColumnType("nvarchar(512)") - .HasMaxLength(512); - - b.Property("OriginalValue") - .HasColumnName("OriginalValue") - .HasColumnType("nvarchar(512)") - .HasMaxLength(512); - - b.Property("PropertyName") - .IsRequired() - .HasColumnName("PropertyName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("PropertyTypeFullName") - .IsRequired() - .HasColumnName("PropertyTypeFullName") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("EntityChangeId"); - - b.ToTable("AbpEntityPropertyChanges"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("Description") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsStatic") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("Regex") - .HasColumnType("nvarchar(512)") - .HasMaxLength(512); - - b.Property("RegexDescription") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Required") - .HasColumnType("bit"); - - b.Property("ValueType") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("AbpClaimTypes"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsDefault") - .HasColumnName("IsDefault") - .HasColumnType("bit"); - - b.Property("IsPublic") - .HasColumnName("IsPublic") - .HasColumnType("bit"); - - b.Property("IsStatic") - .HasColumnName("IsStatic") - .HasColumnType("bit"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedName") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName"); - - b.ToTable("AbpRoles"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("ClaimType") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(1024)") - .HasMaxLength(1024); - - b.Property("RoleId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AbpRoleClaims"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("AccessFailedCount") - .ValueGeneratedOnAdd() - .HasColumnName("AccessFailedCount") - .HasColumnType("int") - .HasDefaultValue(0); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeleterId") - .HasColumnName("DeleterId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeletionTime") - .HasColumnName("DeletionTime") - .HasColumnType("datetime2"); - - b.Property("Email") - .IsRequired() - .HasColumnName("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .ValueGeneratedOnAdd() - .HasColumnName("EmailConfirmed") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnName("IsDeleted") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LastModificationTime") - .HasColumnName("LastModificationTime") - .HasColumnType("datetime2"); - - b.Property("LastModifierId") - .HasColumnName("LastModifierId") - .HasColumnType("uniqueidentifier"); - - b.Property("LockoutEnabled") - .ValueGeneratedOnAdd() - .HasColumnName("LockoutEnabled") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("Name") - .HasColumnName("Name") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("NormalizedEmail") - .IsRequired() - .HasColumnName("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .IsRequired() - .HasColumnName("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnName("PasswordHash") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("PhoneNumber") - .HasColumnName("PhoneNumber") - .HasColumnType("nvarchar(16)") - .HasMaxLength(16); - - b.Property("PhoneNumberConfirmed") - .ValueGeneratedOnAdd() - .HasColumnName("PhoneNumberConfirmed") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("SecurityStamp") - .IsRequired() - .HasColumnName("SecurityStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("Surname") - .HasColumnName("Surname") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("TwoFactorEnabled") - .ValueGeneratedOnAdd() - .HasColumnName("TwoFactorEnabled") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("UserName") - .IsRequired() - .HasColumnName("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("Email"); - - b.HasIndex("NormalizedEmail"); - - b.HasIndex("NormalizedUserName"); - - b.HasIndex("UserName"); - - b.ToTable("AbpUsers"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("ClaimType") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(1024)") - .HasMaxLength(1024); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AbpUserClaims"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => - { - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .IsRequired() - .HasColumnType("nvarchar(196)") - .HasMaxLength(196); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("UserId", "LoginProvider"); - - b.HasIndex("LoginProvider", "ProviderKey"); - - b.ToTable("AbpUserLogins"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => - { - b.Property("OrganizationUnitId") - .HasColumnType("uniqueidentifier"); - - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("OrganizationUnitId", "UserId"); - - b.HasIndex("UserId", "OrganizationUnitId"); - - b.ToTable("AbpUserOrganizationUnits"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.Property("RoleId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId", "UserId"); - - b.ToTable("AbpUserRoles"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uniqueidentifier"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AbpUserTokens"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Code") - .IsRequired() - .HasColumnName("Code") - .HasColumnType("nvarchar(95)") - .HasMaxLength(95); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeleterId") - .HasColumnName("DeleterId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeletionTime") - .HasColumnName("DeletionTime") - .HasColumnType("datetime2"); - - b.Property("DisplayName") - .IsRequired() - .HasColumnName("DisplayName") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnName("IsDeleted") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LastModificationTime") - .HasColumnName("LastModificationTime") - .HasColumnType("datetime2"); - - b.Property("LastModifierId") - .HasColumnName("LastModifierId") - .HasColumnType("uniqueidentifier"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("Code"); - - b.HasIndex("ParentId"); - - b.ToTable("AbpOrganizationUnits"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => - { - b.Property("OrganizationUnitId") - .HasColumnType("uniqueidentifier"); - - b.Property("RoleId") - .HasColumnType("uniqueidentifier"); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("OrganizationUnitId", "RoleId"); - - b.HasIndex("RoleId", "OrganizationUnitId"); - - b.ToTable("AbpOrganizationUnitRoles"); - }); - - modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ProviderName") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("Name", "ProviderName", "ProviderKey"); - - b.ToTable("AbpPermissionGrants"); - }); - - modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ProviderName") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("Value") - .IsRequired() - .HasColumnType("nvarchar(2048)") - .HasMaxLength(2048); - - b.HasKey("Id"); - - b.HasIndex("Name", "ProviderName", "ProviderKey"); - - b.ToTable("AbpSettings"); - }); - - modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeleterId") - .HasColumnName("DeleterId") - .HasColumnType("uniqueidentifier"); - - b.Property("DeletionTime") - .HasColumnName("DeletionTime") - .HasColumnType("datetime2"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnName("IsDeleted") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LastModificationTime") - .HasColumnName("LastModificationTime") - .HasColumnType("datetime2"); - - b.Property("LastModifierId") - .HasColumnName("LastModifierId") - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.HasKey("Id"); - - b.HasIndex("Name"); - - b.ToTable("AbpTenants"); - }); - - modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => - { - b.Property("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("Value") - .IsRequired() - .HasColumnType("nvarchar(1024)") - .HasMaxLength(1024); - - b.HasKey("TenantId", "Name"); - - b.ToTable("AbpTenantConnectionStrings"); - }); - - modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("EntityId") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("EntityType") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("RepliedCommentId") - .HasColumnType("uniqueidentifier"); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Text") - .IsRequired() - .HasColumnType("nvarchar(512)") - .HasMaxLength(512); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "RepliedCommentId"); - - b.HasIndex("TenantId", "EntityType", "EntityId"); - - b.ToTable("CmsComments"); - }); - - modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("CreatorId") - .HasColumnName("CreatorId") - .HasColumnType("uniqueidentifier"); - - b.Property("EntityId") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("EntityType") - .IsRequired() - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("ReactionName") - .IsRequired() - .HasColumnType("nvarchar(32)") - .HasMaxLength(32); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "EntityType", "EntityId", "ReactionName"); - - b.HasIndex("TenantId", "CreatorId", "EntityType", "EntityId", "ReactionName"); - - b.ToTable("CmsUserReactions"); - }); - - modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("Email") - .IsRequired() - .HasColumnName("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .ValueGeneratedOnAdd() - .HasColumnName("EmailConfirmed") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnName("Name") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("PhoneNumber") - .HasColumnName("PhoneNumber") - .HasColumnType("nvarchar(16)") - .HasMaxLength(16); - - b.Property("PhoneNumberConfirmed") - .ValueGeneratedOnAdd() - .HasColumnName("PhoneNumberConfirmed") - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("Surname") - .HasColumnName("Surname") - .HasColumnType("nvarchar(64)") - .HasMaxLength(64); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.Property("UserName") - .IsRequired() - .HasColumnName("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "Email"); - - b.HasIndex("TenantId", "UserName"); - - b.ToTable("CmsUsers"); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => - { - b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) - .WithMany("Actions") - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => - { - b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) - .WithMany("EntityChanges") - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => - { - b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) - .WithMany("PropertyChanges") - .HasForeignKey("EntityChangeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => - { - b.HasOne("Volo.Abp.Identity.IdentityRole", null) - .WithMany("Claims") - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => - { - b.HasOne("Volo.Abp.Identity.IdentityUser", null) - .WithMany("Claims") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => - { - b.HasOne("Volo.Abp.Identity.IdentityUser", null) - .WithMany("Logins") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => - { - b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) - .WithMany() - .HasForeignKey("OrganizationUnitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Volo.Abp.Identity.IdentityUser", null) - .WithMany("OrganizationUnits") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => - { - b.HasOne("Volo.Abp.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Volo.Abp.Identity.IdentityUser", null) - .WithMany("Roles") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => - { - b.HasOne("Volo.Abp.Identity.IdentityUser", null) - .WithMany("Tokens") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => - { - b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) - .WithMany() - .HasForeignKey("ParentId"); - }); - - modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => - { - b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) - .WithMany("Roles") - .HasForeignKey("OrganizationUnitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Volo.Abp.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => - { - b.HasOne("Volo.Abp.TenantManagement.Tenant", null) - .WithMany("ConnectionStrings") - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Migrations +{ + [DbContext(typeof(UnifiedDbContext))] + partial class UnifiedDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationName") + .HasColumnName("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnName("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnName("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnName("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientName") + .HasColumnName("ClientName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Comments") + .HasColumnName("Comments") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnName("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Exceptions") + .HasColumnName("Exceptions") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("HttpMethod") + .HasColumnName("HttpMethod") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("HttpStatusCode") + .HasColumnName("HttpStatusCode") + .HasColumnType("int"); + + b.Property("ImpersonatorTenantId") + .HasColumnName("ImpersonatorTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("ImpersonatorUserId") + .HasColumnName("ImpersonatorUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnName("Url") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnName("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AbpAuditLogs"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnName("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasColumnName("MethodName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Parameters") + .HasColumnName("Parameters") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ServiceName") + .HasColumnName("ServiceName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AbpAuditLogActions"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangeTime") + .HasColumnName("ChangeTime") + .HasColumnType("datetime2"); + + b.Property("ChangeType") + .HasColumnName("ChangeType") + .HasColumnType("tinyint"); + + b.Property("EntityId") + .IsRequired() + .HasColumnName("EntityId") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("EntityTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasColumnName("EntityTypeFullName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("AbpEntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EntityChangeId") + .HasColumnType("uniqueidentifier"); + + b.Property("NewValue") + .HasColumnName("NewValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("OriginalValue") + .HasColumnName("OriginalValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("PropertyName") + .IsRequired() + .HasColumnName("PropertyName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasColumnName("PropertyTypeFullName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("AbpEntityPropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AbpTenants"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("TenantId", "Name"); + + b.ToTable("AbpTenantConnectionStrings"); + }); + + modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "RepliedCommentId"); + + b.HasIndex("TenantId", "EntityType", "EntityId"); + + b.ToTable("CmsComments"); + }); + + modelBuilder.Entity("Volo.CmsKit.Ratings.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("Star") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("CmsRatings"); + }); + + modelBuilder.Entity("Volo.CmsKit.Reactions.UserReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ReactionName") + .IsRequired() + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "EntityType", "EntityId", "ReactionName"); + + b.HasIndex("TenantId", "CreatorId", "EntityType", "EntityId", "ReactionName"); + + b.ToTable("CmsUserReactions"); + }); + + modelBuilder.Entity("Volo.CmsKit.Users.CmsUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Email"); + + b.HasIndex("TenantId", "UserName"); + + b.ToTable("CmsUsers"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/GlobalCmsKitFeatures.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/GlobalCmsKitFeatures.cs index 646a91ef4f..bb7694262b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/GlobalCmsKitFeatures.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/GlobalCmsKitFeatures.cs @@ -11,11 +11,14 @@ namespace Volo.CmsKit.GlobalFeatures public CommentsFeature Comments => GetFeature(); + public RatingsFeature Ratings => GetFeature(); + public GlobalCmsKitFeatures([NotNull] GlobalFeatureManager featureManager) : base(featureManager) { AddFeature(new ReactionsFeature(this)); AddFeature(new CommentsFeature(this)); + AddFeature(new RatingsFeature(this)); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/RatingsFeature.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/RatingsFeature.cs new file mode 100644 index 0000000000..b982d6cebb --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/GlobalFeatures/RatingsFeature.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Volo.Abp.GlobalFeatures; + +namespace Volo.CmsKit.GlobalFeatures +{ + [GlobalFeatureName(Name)] + public class RatingsFeature : GlobalFeature + { + public const string Name = "CmsKit.Ratings"; + + internal RatingsFeature( + [NotNull] GlobalCmsKitFeatures cmsKit + ) : base(cmsKit) + { + + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs new file mode 100644 index 0000000000..a2f7c779af --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Volo.CmsKit.Ratings +{ + public interface IRatingRepository : IBasicRepository + { + + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs new file mode 100644 index 0000000000..4f8cbee8b3 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -0,0 +1,32 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; + +namespace Volo.CmsKit.Ratings +{ + public class Rating : BasicAggregateRoot, IHasCreationTime, IMustHaveCreator + { + public virtual short Star { get; protected set; } + + public virtual DateTime CreationTime { get; set; } + + public virtual Guid CreatorId { get; set; } + + protected Rating() + { + + } + + public Rating( + Guid id, + [NotNull] short star, + Guid creatorId + ) + : base(id) + { + Star = star; + CreatorId = creatorId; + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index a26b22883e..5c9ef3f7fd 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -8,6 +8,7 @@ using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; using Volo.Abp.Users.EntityFrameworkCore; using Volo.CmsKit.GlobalFeatures; +using Volo.CmsKit.Ratings; namespace Volo.CmsKit.EntityFrameworkCore { @@ -71,6 +72,18 @@ namespace Volo.CmsKit.EntityFrameworkCore b.HasIndex(x => new { x.TenantId, x.RepliedCommentId }); }); } + + if (GlobalFeatureManager.Instance.IsEnabled()) + { + builder.Entity(r => + { + r.ToTable(options.TablePrefix + "Ratings", options.Schema); + + r.ConfigureByConvention(); + + r.Property(x => x.Star).IsRequired(); + }); + } } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs index c528da7d5e..33e57cab37 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitEntityFrameworkCoreModule.cs @@ -3,6 +3,7 @@ using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Modularity; using Volo.Abp.Users.EntityFrameworkCore; using Volo.CmsKit.Comments; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -22,6 +23,7 @@ namespace Volo.CmsKit.EntityFrameworkCore options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs new file mode 100644 index 0000000000..b05db8fcfb --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Ratings +{ + public class EfCoreRatingRepository : EfCoreRepository, IRatingRepository + { + public EfCoreRatingRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} \ No newline at end of file From 6caab2e051db25e6d1716b30fdefa4d1fb5ae0b3 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Tue, 25 Aug 2020 13:19:31 +0300 Subject: [PATCH 03/69] CmsKit: make property name more descriptive --- ...igner.cs => 20200825101525_CmsRatings_Added.Designer.cs} | 6 +++--- ...2_Rating_Added.cs => 20200825101525_CmsRatings_Added.cs} | 4 ++-- .../Migrations/UnifiedDbContextModelSnapshot.cs | 2 +- .../src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs | 6 +++--- .../CmsKitDbContextModelCreatingExtensions.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/{20200825083332_Rating_Added.Designer.cs => 20200825101525_CmsRatings_Added.Designer.cs} (97%) rename modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/{20200825083332_Rating_Added.cs => 20200825101525_CmsRatings_Added.cs} (84%) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs similarity index 97% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs index 8a69c9cb89..3de5568a5a 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.Designer.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs @@ -11,8 +11,8 @@ using Volo.CmsKit.EntityFrameworkCore; namespace Volo.CmsKit.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20200825083332_Rating_Added")] - partial class Rating_Added + [Migration("20200825101525_CmsRatings_Added")] + partial class CmsRatings_Added { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -973,7 +973,7 @@ namespace Volo.CmsKit.Migrations .HasColumnName("CreatorId") .HasColumnType("uniqueidentifier"); - b.Property("Star") + b.Property("StarCount") .HasColumnType("smallint"); b.HasKey("Id"); diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs similarity index 84% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs index e6b7a3dfb3..02ee0c1a53 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825083332_Rating_Added.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Volo.CmsKit.Migrations { - public partial class Rating_Added : Migration + public partial class CmsRatings_Added : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -12,7 +12,7 @@ namespace Volo.CmsKit.Migrations columns: table => new { Id = table.Column(nullable: false), - Star = table.Column(nullable: false), + StarCount = table.Column(nullable: false), CreationTime = table.Column(nullable: false), CreatorId = table.Column(nullable: false) }, diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index 0fd87d6cc2..ebf821eeae 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -971,7 +971,7 @@ namespace Volo.CmsKit.Migrations .HasColumnName("CreatorId") .HasColumnType("uniqueidentifier"); - b.Property("Star") + b.Property("StarCount") .HasColumnType("smallint"); b.HasKey("Id"); diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs index 4f8cbee8b3..9384183e7b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -7,7 +7,7 @@ namespace Volo.CmsKit.Ratings { public class Rating : BasicAggregateRoot, IHasCreationTime, IMustHaveCreator { - public virtual short Star { get; protected set; } + public virtual short StarCount { get; protected set; } public virtual DateTime CreationTime { get; set; } @@ -20,12 +20,12 @@ namespace Volo.CmsKit.Ratings public Rating( Guid id, - [NotNull] short star, + [NotNull] short starCount, Guid creatorId ) : base(id) { - Star = star; + StarCount = starCount; CreatorId = creatorId; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index 5c9ef3f7fd..e013723d18 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -81,7 +81,7 @@ namespace Volo.CmsKit.EntityFrameworkCore r.ConfigureByConvention(); - r.Property(x => x.Star).IsRequired(); + r.Property(x => x.StarCount).IsRequired(); }); } } From 6708166e68fc2e90c25411549d4bcffbee6fa9b5 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Wed, 26 Aug 2020 09:40:35 +0300 Subject: [PATCH 04/69] CmsKit: CmsRatings updated --- ...200826063729_CmsRatings_Added.Designer.cs} | 17 ++++++++++++++- ....cs => 20200826063729_CmsRatings_Added.cs} | 12 +++++++++-- .../UnifiedDbContextModelSnapshot.cs | 15 +++++++++++++ .../Volo/CmsKit/Ratings/RatingConsts.cs | 11 ++++++++++ .../Volo/CmsKit/Ratings/Rating.cs | 21 +++++++++++++++---- .../CmsKitDbContextModelCreatingExtensions.cs | 4 ++++ 6 files changed, 73 insertions(+), 7 deletions(-) rename modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/{20200825101525_CmsRatings_Added.Designer.cs => 20200826063729_CmsRatings_Added.Designer.cs} (96%) rename modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/{20200825101525_CmsRatings_Added.cs => 20200826063729_CmsRatings_Added.cs} (65%) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.Designer.cs similarity index 96% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.Designer.cs index 3de5568a5a..00e1003584 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.Designer.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.Designer.cs @@ -11,7 +11,7 @@ using Volo.CmsKit.EntityFrameworkCore; namespace Volo.CmsKit.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20200825101525_CmsRatings_Added")] + [Migration("20200826063729_CmsRatings_Added")] partial class CmsRatings_Added { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -973,11 +973,26 @@ namespace Volo.CmsKit.Migrations .HasColumnName("CreatorId") .HasColumnType("uniqueidentifier"); + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + b.Property("StarCount") .HasColumnType("smallint"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.HasKey("Id"); + b.HasIndex("TenantId", "EntityType", "EntityId"); + b.ToTable("CmsRatings"); }); diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.cs similarity index 65% rename from modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs rename to modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.cs index 02ee0c1a53..d753d65865 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200825101525_CmsRatings_Added.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20200826063729_CmsRatings_Added.cs @@ -12,14 +12,22 @@ namespace Volo.CmsKit.Migrations columns: table => new { Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + EntityType = table.Column(maxLength: 64, nullable: false), + EntityId = table.Column(maxLength: 64, nullable: false), StarCount = table.Column(nullable: false), - CreationTime = table.Column(nullable: false), - CreatorId = table.Column(nullable: false) + CreatorId = table.Column(nullable: false), + CreationTime = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_CmsRatings", x => x.Id); }); + + migrationBuilder.CreateIndex( + name: "IX_CmsRatings_TenantId_EntityType_EntityId", + table: "CmsRatings", + columns: new[] { "TenantId", "EntityType", "EntityId" }); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index ebf821eeae..43d834479a 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -971,11 +971,26 @@ namespace Volo.CmsKit.Migrations .HasColumnName("CreatorId") .HasColumnType("uniqueidentifier"); + b.Property("EntityId") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + b.Property("StarCount") .HasColumnType("smallint"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.HasKey("Id"); + b.HasIndex("TenantId", "EntityType", "EntityId"); + b.ToTable("CmsRatings"); }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs new file mode 100644 index 0000000000..892b3cef45 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs @@ -0,0 +1,11 @@ +using Volo.CmsKit.Entities; + +namespace Volo.CmsKit.Ratings +{ + public static class RatingConsts + { + public static int MaxEntityTypeLength { get; set; } = CmsEntityConsts.MaxEntityTypeLength; + + public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength; + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs index 9384183e7b..a35fd022bf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -1,5 +1,6 @@ using System; using JetBrains.Annotations; +using Volo.Abp; using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; @@ -7,12 +8,18 @@ namespace Volo.CmsKit.Ratings { public class Rating : BasicAggregateRoot, IHasCreationTime, IMustHaveCreator { + public virtual Guid? TenantId { get; protected set; } + + public virtual string EntityType { get; protected set; } + + public virtual string EntityId { get; protected set; } + public virtual short StarCount { get; protected set; } + + public virtual Guid CreatorId { get; set; } public virtual DateTime CreationTime { get; set; } - public virtual Guid CreatorId { get; set; } - protected Rating() { @@ -20,13 +27,19 @@ namespace Volo.CmsKit.Ratings public Rating( Guid id, - [NotNull] short starCount, - Guid creatorId + [NotNull] string entityType, + [NotNull] string entityId, + short starCount, + Guid creatorId, + Guid? tenantId = null ) : base(id) { + EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType), RatingConsts.MaxEntityTypeLength); + EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId), RatingConsts.MaxEntityIdLength); StarCount = starCount; CreatorId = creatorId; + TenantId = tenantId; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index e013723d18..d73f60967a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -82,6 +82,10 @@ namespace Volo.CmsKit.EntityFrameworkCore r.ConfigureByConvention(); r.Property(x => x.StarCount).IsRequired(); + r.Property(x => x.EntityType).IsRequired().HasMaxLength(RatingConsts.MaxEntityTypeLength); + r.Property(x => x.EntityId).IsRequired().HasMaxLength(RatingConsts.MaxEntityIdLength); + + r.HasIndex(x => new {x.TenantId, x.EntityType, x.EntityId}); }); } } From d2458fb2ff293a8b51826390b246e56d5b2509fd Mon Sep 17 00:00:00 2001 From: EngincanV Date: Wed, 26 Aug 2020 10:54:20 +0300 Subject: [PATCH 05/69] CmsKit: Rating app service and repository implementations --- .../Volo/CmsKit/Ratings/IRatingRepository.cs | 10 ++- .../Volo/CmsKit/Ratings/Rating.cs | 14 +++- .../CmsKit/Ratings/EfCoreRatingRepository.cs | 18 ++++ .../CmsKit/MongoDB/CmsKitMongoDbContext.cs | 3 + .../MongoDB/CmsKitMongoDbContextExtensions.cs | 6 ++ .../CmsKit/MongoDB/CmsKitMongoDbModule.cs | 3 + .../CmsKit/MongoDB/ICmsKitMongoDbContext.cs | 3 + .../MongoDB/Ratings/MongoRatingRepository.cs | 31 +++++++ ...CmsKit.Public.Application.Contracts.csproj | 30 +++---- .../Public/Ratings/CreateRatingInput.cs | 10 +++ .../Public/Ratings/IRatingPublicAppService.cs | 18 ++++ .../Volo/CmsKit/Public/Ratings/RatingDto.cs | 19 +++++ .../Public/Ratings/UpdateRatingInput.cs | 10 +++ .../Volo.CmsKit.Public.Application.csproj | 32 ++++---- .../Public/Ratings/RatingPublicAppService.cs | 82 +++++++++++++++++++ 15 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingDto.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs index a2f7c779af..e7a25b1709 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs @@ -1,10 +1,18 @@ using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; using Volo.Abp.Domain.Repositories; namespace Volo.CmsKit.Ratings { public interface IRatingRepository : IBasicRepository { - + Task> GetListAsync( + [NotNull] string entityType, + [NotNull] string entityId, + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs index a35fd022bf..4567b5d275 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -37,9 +37,21 @@ namespace Volo.CmsKit.Ratings { EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType), RatingConsts.MaxEntityTypeLength); EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId), RatingConsts.MaxEntityIdLength); - StarCount = starCount; + SetStarCount(starCount); CreatorId = creatorId; TenantId = tenantId; } + + public virtual void SetStarCount(short starCount) + { + if(starCount <= 5 && starCount > 0) + { + StarCount = starCount; + } + else + { + throw new ArgumentOutOfRangeException("Choosen star must between 1 and 5"); + } + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs index b05db8fcfb..36ed50ad35 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -1,4 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; using Volo.CmsKit.EntityFrameworkCore; @@ -10,5 +17,16 @@ namespace Volo.CmsKit.Ratings public EfCoreRatingRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { } + + public async Task> GetListAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var query = DbSet.Where(r => r.EntityType == entityType && r.EntityId == entityId); + var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); + + return ratings; + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs index 63bf48a14e..09053167f9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContext.cs @@ -2,6 +2,7 @@ using Volo.Abp.Data; using Volo.Abp.MongoDB; using Volo.CmsKit.Comments; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -16,6 +17,8 @@ namespace Volo.CmsKit.MongoDB public IMongoCollection CmsUsers => Collection(); + public IMongoCollection Ratings => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContextExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContextExtensions.cs index 568e136591..2dc4c55a71 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContextExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbContextExtensions.cs @@ -2,6 +2,7 @@ using Volo.Abp; using Volo.Abp.MongoDB; using Volo.CmsKit.Comments; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -35,6 +36,11 @@ namespace Volo.CmsKit.MongoDB { x.CollectionName = CmsKitDbProperties.DbTablePrefix + "Comments"; }); + + builder.Entity(x => + { + x.CollectionName = CmsKitDbProperties.DbTablePrefix + "Ratings"; + }); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs index 736b30ada1..d479821b38 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/CmsKitMongoDbModule.cs @@ -4,8 +4,10 @@ using Volo.Abp.MongoDB; using Volo.Abp.Users.MongoDB; using Volo.CmsKit.Comments; using Volo.CmsKit.MongoDB.Comments; +using Volo.CmsKit.MongoDB.Ratings; using Volo.CmsKit.MongoDB.Reactions; using Volo.CmsKit.MongoDB.Users; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -25,6 +27,7 @@ namespace Volo.CmsKit.MongoDB options.AddRepository(); options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs index 16b72c8902..40914a9720 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/ICmsKitMongoDbContext.cs @@ -2,6 +2,7 @@ using Volo.Abp.Data; using Volo.Abp.MongoDB; using Volo.CmsKit.Comments; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -15,5 +16,7 @@ namespace Volo.CmsKit.MongoDB IMongoCollection Comments { get; } IMongoCollection CmsUsers { get; } + + IMongoCollection Ratings { get; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs new file mode 100644 index 0000000000..a7b5fa33b2 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; +using Volo.CmsKit.Ratings; + +namespace Volo.CmsKit.MongoDB.Ratings +{ + public class MongoRatingRepository : MongoDbRepository, IRatingRepository + { + public MongoRatingRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task> GetListAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var query = GetMongoQueryable().Where(r => r.EntityType == entityType && r.EntityId == entityId); + var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); + + return ratings; + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo.CmsKit.Public.Application.Contracts.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo.CmsKit.Public.Application.Contracts.csproj index f44c745740..8515d28e83 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo.CmsKit.Public.Application.Contracts.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo.CmsKit.Public.Application.Contracts.csproj @@ -1,15 +1,15 @@ - - - - - - - netstandard2.0 - - - - - - - - + + + + + + + netstandard2.0 + + + + + + + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs new file mode 100644 index 0000000000..88b53d5b0c --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.CmsKit.Public.Ratings +{ + public class CreateRatingInput + { + [Required] + public short StarCount { get; set; } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs new file mode 100644 index 0000000000..d72d9dd365 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Volo.CmsKit.Public.Ratings +{ + public interface IRatingPublicAppService : IApplicationService + { + Task> GetListAsync(string entityType, string entityId); + + Task CreateAsync(string entityType, string entityId, CreateRatingInput input); + + Task UpdateAsync(Guid id, UpdateRatingInput input); + + Task DeleteAsync(Guid id); + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingDto.cs new file mode 100644 index 0000000000..bf050465c9 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingDto.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.CmsKit.Public.Ratings +{ + public class RatingDto + { + public Guid Id { get; set; } + + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public short StarCount { get; set; } + + public Guid CreatorId { get; set; } + + public DateTime CreationTime { get; set; } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs new file mode 100644 index 0000000000..e2f77122d4 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.CmsKit.Public.Ratings +{ + public class UpdateRatingInput + { + [Required] + public short StarCount { get; set; } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj index a8a9613fbf..702523e3c6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj @@ -1,16 +1,16 @@ - - - - - - - netstandard2.0 - - - - - - - - - + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs new file mode 100644 index 0000000000..4eb5e8e9ed --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Authorization; +using Volo.Abp.Users; +using Volo.CmsKit.Ratings; +using Volo.CmsKit.Users; + +namespace Volo.CmsKit.Public.Ratings +{ + public class RatingPublicAppService : ApplicationService, IRatingPublicAppService + { + protected IRatingRepository RatingRepository { get; } + public ICmsUserLookupService CmsUserLookupService { get; } + + public RatingPublicAppService(IRatingRepository ratingRepository, ICmsUserLookupService cmsUserLookupService) + { + RatingRepository = ratingRepository; + CmsUserLookupService = cmsUserLookupService; + } + + public virtual async Task> GetListAsync(string entityType, string entityId) + { + var ratings = await RatingRepository.GetListAsync(entityType, entityId); + var ratingDto = ObjectMapper.Map, List>(ratings); + + return new ListResultDto(ratingDto); + } + + [Authorize] + public virtual async Task CreateAsync(string entityType, string entityId, CreateRatingInput input) + { + var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId()); + + var rating = await RatingRepository.InsertAsync( + new Rating( + GuidGenerator.Create(), + entityType, + entityId, + input.StarCount, + user.Id, + CurrentTenant.Id + ) + ); + + return ObjectMapper.Map(rating); + } + + [Authorize] + public virtual async Task UpdateAsync(Guid id, UpdateRatingInput input) + { + var rating = await RatingRepository.GetAsync(id); + + if (rating.CreatorId != CurrentUser.GetId()) + { + throw new AbpAuthorizationException(); + } + + rating.SetStarCount(input.StarCount); + + var updatedRating = await RatingRepository.UpdateAsync(rating); + + return ObjectMapper.Map(updatedRating); + } + + [Authorize] + public virtual async Task DeleteAsync(Guid id) + { + var rating = await RatingRepository.GetAsync(id); + + if (rating.CreatorId != CurrentUser.GetId()) + { + throw new AbpAuthorizationException(); + } + + await RatingRepository.DeleteAsync(id); + } + } +} \ No newline at end of file From dd13fc2b17624194186fd3c970c4e905e5c4bf1a Mon Sep 17 00:00:00 2001 From: EngincanV Date: Wed, 26 Aug 2020 16:22:33 +0300 Subject: [PATCH 06/69] CmsKit: GetCurrentUserRatingAsync added --- .../Volo/CmsKit/Ratings/IRatingRepository.cs | 7 +++ .../CmsKit/Ratings/EfCoreRatingRepository.cs | 10 ++++ .../MongoDB/Ratings/MongoRatingRepository.cs | 13 ++++ .../Public/Ratings/IRatingPublicAppService.cs | 4 +- .../PublicApplicationAutoMapperProfile.cs | 4 ++ .../Public/Ratings/RatingPublicAppService.cs | 10 ++++ .../Volo.CmsKit.Public.HttpApi.csproj | 32 +++++----- .../Public/Ratings/RatingPublicController.cs | 59 +++++++++++++++++++ .../CmsKitPublicWidgetsController.cs | 4 +- .../Components/Rating/RatingViewComponent.cs | 56 +++++++++++++++++- 10 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs index e7a25b1709..d889c4b678 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs @@ -14,5 +14,12 @@ namespace Volo.CmsKit.Ratings [NotNull] string entityId, CancellationToken cancellationToken = default ); + + Task GetCurrentUserRatingAsync( + [NotNull] string entityType, + [NotNull] string entityId, + Guid userId, + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs index 36ed50ad35..b7e53e9b59 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -28,5 +28,15 @@ namespace Volo.CmsKit.Ratings return ratings; } + + public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var rating = await DbSet.FirstOrDefaultAsync(r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, GetCancellationToken(cancellationToken)); + + return rating; + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs index a7b5fa33b2..93509233f0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -27,5 +27,18 @@ namespace Volo.CmsKit.MongoDB.Ratings return ratings; } + + public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, + CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var rating = await GetMongoQueryable() + .FirstOrDefaultAsync(r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, + GetCancellationToken(cancellationToken)); + + return rating; + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs index d72d9dd365..79785913bb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs @@ -8,11 +8,13 @@ namespace Volo.CmsKit.Public.Ratings public interface IRatingPublicAppService : IApplicationService { Task> GetListAsync(string entityType, string entityId); - + Task CreateAsync(string entityType, string entityId, CreateRatingInput input); Task UpdateAsync(Guid id, UpdateRatingInput input); Task DeleteAsync(Guid id); + + Task GetCurrentUserRatingAsync(string entityType, string entityId); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs index e0b64bb0e8..3ac2d1949b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs @@ -2,6 +2,8 @@ using Volo.Abp.AutoMapper; using Volo.CmsKit.Comments; using Volo.CmsKit.Public.Comments; +using Volo.CmsKit.Public.Ratings; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Users; namespace Volo.CmsKit.Public @@ -18,6 +20,8 @@ namespace Volo.CmsKit.Public CreateMap() .Ignore(x=> x.Replies) .Ignore(x=> x.Author); + + CreateMap(); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs index 4eb5e8e9ed..43bc6546aa 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs @@ -78,5 +78,15 @@ namespace Volo.CmsKit.Public.Ratings await RatingRepository.DeleteAsync(id); } + + [Authorize] + public virtual async Task GetCurrentUserRatingAsync(string entityType, string entityId) + { + var currentUserId = CurrentUser.GetId(); + + var rating = await RatingRepository.GetCurrentUserRatingAsync(entityType, entityId, currentUserId); + + return ObjectMapper.Map(rating); + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo.CmsKit.Public.HttpApi.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo.CmsKit.Public.HttpApi.csproj index da54b3d2bf..ec87dac00f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo.CmsKit.Public.HttpApi.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo.CmsKit.Public.HttpApi.csproj @@ -1,16 +1,16 @@ - - - - - - - netcoreapp3.1 - - - - - - - - - + + + + + + + netcoreapp3.1 + + + + + + + + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs new file mode 100644 index 0000000000..c6f6355e75 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.GlobalFeatures; +using Volo.CmsKit.GlobalFeatures; + +namespace Volo.CmsKit.Public.Ratings +{ + [RequiresGlobalFeature(typeof(RatingsFeature))] + [RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] + [Area("cms-kit")] + [Route("api/cms-kit-public/ratings")] + public class RatingPublicController : CmsKitPublicControllerBase, IRatingPublicAppService + { + protected IRatingPublicAppService RatingPublicAppService { get; } + + public RatingPublicController(IRatingPublicAppService ratingPublicAppService) + { + RatingPublicAppService = ratingPublicAppService; + } + + [HttpGet] + [Route("{entityType}/{entityId}")] + public virtual Task> GetListAsync(string entityType, string entityId) + { + return RatingPublicAppService.GetListAsync(entityType, entityId); + } + + [HttpPut] + [Route("{entityType}/{entityId}")] + public virtual Task CreateAsync(string entityType, string entityId, CreateRatingInput input) + { + return RatingPublicAppService.CreateAsync(entityType, entityId, input); + } + + [HttpPut] + [Route("{id}")] + public virtual Task UpdateAsync(Guid id, UpdateRatingInput input) + { + return RatingPublicAppService.UpdateAsync(id, input); + } + + [HttpDelete] + [Route("{id}")] + public Task DeleteAsync(Guid id) + { + return RatingPublicAppService.DeleteAsync(id); + } + + [HttpGet] + [Route("{entityType}/{entityId}")] + public Task GetCurrentUserRatingAsync(string entityType, string entityId) + { + return RatingPublicAppService.GetCurrentUserRatingAsync(entityType, entityId); + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs index 7cc9c410c4..e0b1f9d6f3 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicWidgetsController.cs @@ -18,9 +18,9 @@ namespace Volo.CmsKit.Public.Web.Controllers return Task.FromResult((IActionResult)ViewComponent(typeof(CommentingViewComponent), new {entityType, entityId})); } - public Task Rating() + public Task Rating(string entityType, string entityId) { - return Task.FromResult((IActionResult) ViewComponent(typeof(RatingViewComponent))); + return Task.FromResult((IActionResult) ViewComponent(typeof(RatingViewComponent), new {entityType, entityId})); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs index f77759ac20..cabae790c0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -1,6 +1,12 @@ -using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.Users; +using Volo.CmsKit.Public.Ratings; namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating { @@ -12,9 +18,53 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating )] public class RatingViewComponent : AbpViewComponent { - public IViewComponentResult Invoke() + public IRatingPublicAppService RatingPublicAppService { get; } + public AbpMvcUiOptions AbpMvcUiOptions { get; } + public ICurrentUser CurrentUser { get; } + + public RatingViewComponent(IRatingPublicAppService ratingPublicAppService, IOptions options, ICurrentUser currentUser) + { + RatingPublicAppService = ratingPublicAppService; + AbpMvcUiOptions = options.Value; + CurrentUser = currentUser; + } + + public virtual async Task InvokeAsync(string entityType, string entityId) { - return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml"); + var ratings = await RatingPublicAppService.GetListAsync(entityType, entityId); + + RatingDto currentUserRating = null; + if (CurrentUser.IsAuthenticated) + { + currentUserRating = await RatingPublicAppService.GetCurrentUserRatingAsync(entityType, entityId); + } + + var loginUrl = + $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}"; + + var viewModel = new RatingViewModel + { + EntityId = entityId, + EntityType = entityType, + LoginUrl = loginUrl, + Ratings = ratings.Items, + CurrentRating = currentUserRating + }; + + return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml", viewModel); } } + + public class RatingViewModel + { + public string EntityType { get; set; } + + public string EntityId { get; set; } + + public string LoginUrl { get; set; } + + public IReadOnlyList Ratings { get; set; } + + public RatingDto CurrentRating { get; set; } + } } \ No newline at end of file From 404fe5e8a014a0aae005171305ab31db7cf57b59 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Thu, 27 Aug 2020 11:52:34 +0300 Subject: [PATCH 07/69] CmsKit: ratings group by star count --- .../Pages/Index.cshtml | 5 ++++ .../Volo/CmsKit/Ratings/IRatingRepository.cs | 6 +++++ .../RatingWithStarCountQueryResultItem.cs | 9 ++++++++ .../CmsKit/Ratings/EfCoreRatingRepository.cs | 21 +++++++++++++++++ .../MongoDB/Ratings/MongoRatingRepository.cs | 23 +++++++++++++++++++ .../Public/Ratings/IRatingPublicAppService.cs | 3 +++ .../Public/Ratings/RatingWithStarCountDto.cs | 9 ++++++++ .../PublicApplicationAutoMapperProfile.cs | 2 ++ .../Public/Ratings/RatingPublicAppService.cs | 7 ++++++ .../Public/Ratings/RatingPublicController.cs | 12 ++++++++-- .../Components/Rating/RatingViewComponent.cs | 6 ++--- 11 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/RatingWithStarCountQueryResultItem.cs create mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml index 253016f03b..ab628d4412 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml @@ -5,6 +5,7 @@ @using Volo.CmsKit.GlobalFeatures @using Volo.CmsKit.Pages @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection @model IndexModel @inject IStringLocalizer Localizer @@ -57,3 +58,7 @@ } + +
+@await Component.InvokeAsync(typeof(RatingViewComponent), new { entityType = "quote", entityId = "2" }) +
diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs index d889c4b678..29ce07df63 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs @@ -21,5 +21,11 @@ namespace Volo.CmsKit.Ratings Guid userId, CancellationToken cancellationToken = default ); + + Task> GetGroupedStarCountsAsync( + [NotNull] string entityType, + [NotNull] string entityId, + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/RatingWithStarCountQueryResultItem.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/RatingWithStarCountQueryResultItem.cs new file mode 100644 index 0000000000..5f53074b97 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/RatingWithStarCountQueryResultItem.cs @@ -0,0 +1,9 @@ +namespace Volo.CmsKit.Ratings +{ + public class RatingWithStarCountQueryResultItem + { + public short StarCount { get; set; } + + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs index b7e53e9b59..b9f210cdcf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -38,5 +38,26 @@ namespace Volo.CmsKit.Ratings return rating; } + + public async Task> GetGroupedStarCountsAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var query = from rating in DbSet + where rating.EntityType == entityType && rating.EntityId == entityId + orderby rating.StarCount descending + group rating by rating.StarCount + into g + select new RatingWithStarCountQueryResultItem + { + StarCount = g.Key, + Count = g.Count() + }; + + var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); + + return ratings; + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs index 93509233f0..a58b0517f4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -40,5 +41,27 @@ namespace Volo.CmsKit.MongoDB.Ratings return rating; } + + public async Task> GetGroupedStarCountsAsync(string entityType, + string entityId, CancellationToken cancellationToken = default) + { + Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); + Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); + + var query = from rating in GetMongoQueryable() + where rating.EntityType == entityType && rating.EntityId == entityId + orderby rating.StarCount descending + group rating by rating.StarCount + into g + select new RatingWithStarCountQueryResultItem + { + StarCount = g.Key, + Count = g.Count() + }; + + var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); + + return ratings; + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs index 79785913bb..9e4c8c1f11 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -16,5 +17,7 @@ namespace Volo.CmsKit.Public.Ratings Task DeleteAsync(Guid id); Task GetCurrentUserRatingAsync(string entityType, string entityId); + + Task> GetGroupedStarCountsAsync(string entityType, string entityId); } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs new file mode 100644 index 0000000000..e8f60ea49b --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs @@ -0,0 +1,9 @@ +namespace Volo.CmsKit.Public.Ratings +{ + public class RatingWithStarCountDto + { + public short StarCount { get; set; } + + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs index 3ac2d1949b..0a0a5403b1 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs @@ -22,6 +22,8 @@ namespace Volo.CmsKit.Public .Ignore(x=> x.Author); CreateMap(); + + CreateMap(); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs index 43bc6546aa..7621924727 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs @@ -88,5 +88,12 @@ namespace Volo.CmsKit.Public.Ratings return ObjectMapper.Map(rating); } + + public virtual async Task> GetGroupedStarCountsAsync(string entityType, string entityId) + { + var ratings = await RatingRepository.GetGroupedStarCountsAsync(entityType, entityId); + + return ObjectMapper.Map, List>(ratings); + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs index c6f6355e75..25cc40ea5a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp; @@ -44,16 +45,23 @@ namespace Volo.CmsKit.Public.Ratings [HttpDelete] [Route("{id}")] - public Task DeleteAsync(Guid id) + public virtual Task DeleteAsync(Guid id) { return RatingPublicAppService.DeleteAsync(id); } [HttpGet] [Route("{entityType}/{entityId}")] - public Task GetCurrentUserRatingAsync(string entityType, string entityId) + public virtual Task GetCurrentUserRatingAsync(string entityType, string entityId) { return RatingPublicAppService.GetCurrentUserRatingAsync(entityType, entityId); } + + [HttpGet] + [Route("{entityType}/{entityId}")] + public virtual Task> GetGroupedStarCountsAsync(string entityType, string entityId) + { + return RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId); + } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs index cabae790c0..31dedbc532 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -31,7 +31,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating public virtual async Task InvokeAsync(string entityType, string entityId) { - var ratings = await RatingPublicAppService.GetListAsync(entityType, entityId); + var ratings = await RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId); RatingDto currentUserRating = null; if (CurrentUser.IsAuthenticated) @@ -47,7 +47,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating EntityId = entityId, EntityType = entityType, LoginUrl = loginUrl, - Ratings = ratings.Items, + Ratings = ratings, CurrentRating = currentUserRating }; @@ -63,7 +63,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating public string LoginUrl { get; set; } - public IReadOnlyList Ratings { get; set; } + public List Ratings { get; set; } public RatingDto CurrentRating { get; set; } } From 3cc99ecd30058d9928a1c498eba5193daee6eed5 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Thu, 27 Aug 2020 12:31:07 +0300 Subject: [PATCH 08/69] CmsKit: order by desc bugfix --- .../CmsKit/Ratings/EfCoreRatingRepository.cs | 20 ++++++++++++------- .../MongoDB/Ratings/MongoRatingRepository.cs | 15 ++++++++------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs index b9f210cdcf..577f2e3a7d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -18,7 +18,8 @@ namespace Volo.CmsKit.Ratings { } - public async Task> GetListAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + public async Task> GetListAsync(string entityType, string entityId, + CancellationToken cancellationToken = default) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); @@ -29,31 +30,36 @@ namespace Volo.CmsKit.Ratings return ratings; } - public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, CancellationToken cancellationToken = default) + public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, + CancellationToken cancellationToken = default) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - var rating = await DbSet.FirstOrDefaultAsync(r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, GetCancellationToken(cancellationToken)); + var rating = await DbSet.FirstOrDefaultAsync( + r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, + GetCancellationToken(cancellationToken)); return rating; } - public async Task> GetGroupedStarCountsAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + public async Task> GetGroupedStarCountsAsync(string entityType, + string entityId, CancellationToken cancellationToken = default) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - var query = from rating in DbSet + var query = ( + from rating in DbSet where rating.EntityType == entityType && rating.EntityId == entityId - orderby rating.StarCount descending group rating by rating.StarCount into g select new RatingWithStarCountQueryResultItem { StarCount = g.Key, Count = g.Count() - }; + } + ).OrderByDescending(r => r.StarCount); var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs index a58b0517f4..ecf389cde7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -14,11 +14,13 @@ namespace Volo.CmsKit.MongoDB.Ratings { public class MongoRatingRepository : MongoDbRepository, IRatingRepository { - public MongoRatingRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + public MongoRatingRepository(IMongoDbContextProvider dbContextProvider) : base( + dbContextProvider) { } - public async Task> GetListAsync(string entityType, string entityId, CancellationToken cancellationToken = default) + public async Task> GetListAsync(string entityType, string entityId, + CancellationToken cancellationToken = default) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); @@ -38,7 +40,7 @@ namespace Volo.CmsKit.MongoDB.Ratings var rating = await GetMongoQueryable() .FirstOrDefaultAsync(r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, GetCancellationToken(cancellationToken)); - + return rating; } @@ -48,16 +50,17 @@ namespace Volo.CmsKit.MongoDB.Ratings Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - var query = from rating in GetMongoQueryable() + var query = ( + from rating in GetMongoQueryable() where rating.EntityType == entityType && rating.EntityId == entityId - orderby rating.StarCount descending group rating by rating.StarCount into g select new RatingWithStarCountQueryResultItem { StarCount = g.Key, Count = g.Count() - }; + } + ).OrderByDescending(r => r.StarCount); var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); From a57c1b975f36b84e63df1616a47f3a6dbe962580 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Thu, 27 Aug 2020 14:48:55 +0300 Subject: [PATCH 09/69] CmsKit: Rating missing tests --- .../Ratings/RatingPublicAppService_Tests.cs | 103 ++++++++++++++++++ .../Ratings/RatingRepository_Tests.cs | 9 ++ .../MongoDB/Ratings/RatingRepository_Tests.cs | 11 ++ .../CmsKitDataSeedContributor.cs | 47 +++++++- .../Ratings/RatingRepository_Tests.cs | 49 +++++++++ 5 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs create mode 100644 modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Ratings/RatingRepository_Tests.cs create mode 100644 modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Ratings/RatingRepository_Tests.cs create mode 100644 modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs new file mode 100644 index 0000000000..1a21da8425 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs @@ -0,0 +1,103 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Volo.Abp.Users; +using Volo.CmsKit.Public.Ratings; +using Xunit; + +namespace Volo.CmsKit.Ratings +{ + public class RatingPublicAppService_Tests : CmsKitApplicationTestBase + { + private readonly IRatingPublicAppService _ratingAppService; + private ICurrentUser _currentUser; + private readonly CmsKitTestData _cmsKitTestData; + + public RatingPublicAppService_Tests() + { + _ratingAppService = GetRequiredService(); + _cmsKitTestData = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _currentUser = Substitute.For(); + services.AddSingleton(_currentUser); + } + + [Fact] + public async Task CreateAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var newRating = await _ratingAppService.CreateAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + new CreateRatingInput + { + StarCount = 4 + }); + + UsingDbContext(context => + { + var ratings = context.Set().Where(x => + x.EntityId == _cmsKitTestData.EntityId1 && x.EntityType == _cmsKitTestData.EntityType1).ToList(); + + ratings + .Any(c => c.Id == newRating.Id && c.CreatorId == newRating.CreatorId && c.StarCount == newRating.StarCount) + .ShouldBeTrue(); + }); + } + + [Fact] + public async Task UpdateAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var rating = await _ratingAppService.CreateAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + new CreateRatingInput + { + StarCount = 4 + }); + + await _ratingAppService.UpdateAsync(rating.Id, new UpdateRatingInput + { + StarCount = 5 + }); + + UsingDbContext(context => + { + var updatedRating = context.Set().Single(x => x.Id == rating.Id); + + updatedRating.StarCount.ShouldBe((short)5); + }); + } + + [Fact] + public async Task DeleteAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var rating = await _ratingAppService.CreateAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + new CreateRatingInput + { + StarCount = 4 + }); + + await _ratingAppService.DeleteAsync(rating.Id); + + UsingDbContext(context => + { + var deletedComment = context.Set().FirstOrDefault(x => x.Id == rating.Id); + + deletedComment.ShouldBeNull(); + }); + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Ratings/RatingRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Ratings/RatingRepository_Tests.cs new file mode 100644 index 0000000000..390ddd1cb3 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/Ratings/RatingRepository_Tests.cs @@ -0,0 +1,9 @@ +using Volo.CmsKit.Ratings; + +namespace Volo.CmsKit.EntityFrameworkCore.Ratings +{ + public class RatingRepository_Tests : RatingRepository_Tests + { + + } +} \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Ratings/RatingRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Ratings/RatingRepository_Tests.cs new file mode 100644 index 0000000000..c0814253e6 --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/Ratings/RatingRepository_Tests.cs @@ -0,0 +1,11 @@ +using Volo.CmsKit.Ratings; +using Xunit; + +namespace Volo.CmsKit.MongoDB.Ratings +{ + [Collection(MongoTestCollection.Name)] + public class RatingRepository_Tests : RatingRepository_Tests + { + + } +} \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs index 12c6255623..f91e79fb84 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs @@ -4,6 +4,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.Users; using Volo.CmsKit.Comments; +using Volo.CmsKit.Ratings; using Volo.CmsKit.Reactions; using Volo.CmsKit.Users; @@ -16,19 +17,22 @@ namespace Volo.CmsKit private readonly CmsKitTestData _cmsKitTestData; private readonly ICommentRepository _commentRepository; private readonly ReactionManager _reactionManager; + private readonly IRatingRepository _ratingRepository; public CmsKitDataSeedContributor( IGuidGenerator guidGenerator, ICmsUserRepository cmsUserRepository, CmsKitTestData cmsKitTestData, ICommentRepository commentRepository, - ReactionManager reactionManager) + ReactionManager reactionManager, + IRatingRepository ratingRepository) { _guidGenerator = guidGenerator; _cmsUserRepository = cmsUserRepository; _cmsKitTestData = cmsKitTestData; _commentRepository = commentRepository; _reactionManager = reactionManager; + _ratingRepository = ratingRepository; } public async Task SeedAsync(DataSeedContext context) @@ -38,13 +42,17 @@ namespace Volo.CmsKit await SeedCommentsAsync(); await SeedReactionsAsync(); + + await SeedRatingsAsync(); } private async Task SeedUsersAsync() { - await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User1Id, "user1", "user1@volo.com", + await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User1Id, "user1", + "user1@volo.com", "user", "1"))); - await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User2Id, "user2", "user2@volo.com", + await _cmsUserRepository.InsertAsync(new CmsUser(new UserData(_cmsKitTestData.User2Id, "user2", + "user2@volo.com", "user", "2"))); } @@ -131,5 +139,36 @@ namespace Volo.CmsKit _cmsKitTestData.EntityId1, StandardReactions.ThumbsUp); } + + private async Task SeedRatingsAsync() + { + await _ratingRepository.InsertAsync(new Rating(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + 4, + _cmsKitTestData.User1Id + )); + + await _ratingRepository.InsertAsync(new Rating(_guidGenerator.Create(), + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, + 5, + _cmsKitTestData.User1Id + )); + + await _ratingRepository.InsertAsync(new Rating(_guidGenerator.Create(), + _cmsKitTestData.EntityType2, + _cmsKitTestData.EntityId2, + 5, + _cmsKitTestData.User2Id + )); + + await _ratingRepository.InsertAsync(new Rating(_guidGenerator.Create(), + _cmsKitTestData.EntityType2, + _cmsKitTestData.EntityId2, + 1, + _cmsKitTestData.User2Id + )); + } } -} +} \ No newline at end of file diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs new file mode 100644 index 0000000000..b09b380f8e --- /dev/null +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.CmsKit.Ratings +{ + public abstract class RatingRepository_Tests : CmsKitTestBase + where TStartupModule : IAbpModule + { + private readonly CmsKitTestData _cmsKitTestData; + private readonly IRatingRepository _ratingRepository; + + public RatingRepository_Tests() + { + _cmsKitTestData = GetRequiredService(); + _ratingRepository = GetRequiredService(); + } + + [Fact] + public async Task GetListAsync() + { + var list = await _ratingRepository.GetListAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); + + list.Count.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task GetCurrentUserRatingAsync() + { + var userRating = await _ratingRepository.GetCurrentUserRatingAsync(_cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1, _cmsKitTestData.User1Id); + + userRating.ShouldNotBeNull(); + userRating.EntityId.ShouldBe(_cmsKitTestData.EntityId1); + userRating.EntityType.ShouldBe(_cmsKitTestData.EntityType1); + userRating.CreatorId.ShouldBe(_cmsKitTestData.User1Id); + } + + [Fact] + public async Task GetGroupedStarCountsAsync() + { + var list = await _ratingRepository.GetGroupedStarCountsAsync(_cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1); + + list.ShouldNotBeNull(); + } + } +} \ No newline at end of file From 23ac23eb6b7afdc2530c0277b8857a7bae7cf8c0 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Thu, 27 Aug 2020 15:21:18 +0300 Subject: [PATCH 10/69] CmsKit: Rating widget --- .../Shared/Components/Rating/Default.cshtml | 86 ++++++++++++++----- .../Shared/Components/Rating/default.css | 36 +------- .../Shared/Components/Rating/default.js | 82 +++++++++++++++++- 3 files changed, 145 insertions(+), 59 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index f9cd651ff1..7c02d769a5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -1,27 +1,67 @@ -@using Volo.Abp.Users +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Users +@using Volo.CmsKit.Localization @inject ICurrentUser CurrentUser +@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating.RatingViewModel +@inject IHtmlLocalizer L -@if (CurrentUser.IsAuthenticated) -{ -
-

My Simple Widget

-

This is a simple widget!

-
-} + -
- - +
+ @if (CurrentUser.IsAuthenticated) + { +
+ + @(Model.CurrentRating != null ? Model.CurrentRating.StarCount : 0) + if (Model.CurrentRating != null) + { + + @L["Undo"] + + } +
+
+

All Stars

+ @if (Model.Ratings != null) + { +
    + @foreach (var rating in Model.Ratings) + { +
  • + @rating.StarCount @L["Stars"] + @rating.Count +
  • + } +
+ } +
+ } + else + { + + } +
- - - - - - - - - - - -
\ No newline at end of file + + + \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css index 0dc4780ee9..5f282702bb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css @@ -1,35 +1 @@ -.rate { - float: left; - height: 46px; - padding: 0 10px; -} -.rate:not(:checked) > input { - position:absolute; - top:-9999px; -} -.rate:not(:checked) > label { - float:right; - width:1em; - overflow:hidden; - white-space:nowrap; - cursor:pointer; - font-size:30px; - color:#ccc; -} -.rate:not(:checked) > label:before { - content: '★ '; -} -.rate > input:checked ~ label { - color: #ffc700; -} -.rate:not(:checked) > label:hover, -.rate:not(:checked) > label:hover ~ label { - color: #deb217; -} -.rate > input:checked + label:hover, -.rate > input:checked + label:hover ~ label, -.rate > input:checked ~ label:hover, -.rate > input:checked ~ label:hover ~ label, -.rate > label:hover ~ input:checked ~ label { - color: #c59b08; -} \ No newline at end of file + \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js index 5f282702bb..1b29a47190 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -1 +1,81 @@ - \ No newline at end of file +(function () { + + var l = abp.localization.getResource("CmsKit"); + + $(document).ready(function () { + abp.widgets.CmsRating = function ($widget) { + var widgetManager = $widget.data("abp-widget-manager"); + var $ratingArea = $widget.find(".cms-rating-area"); + + function getFilters() { + return { + entityType: $ratingArea.attr("data-entity-type"), + entityId: $ratingArea.attr("data-entity-id") + }; + } + + function registerCreateOfNewRating() { + $(".my-rating-5").starRating({ + initialRating: 0, + disableAfterRate: true, + useFullStars: true, + readOnly: authenticated === "True", + onHover: function(currentIndex, currentRating, $el) { + $(".live-rating").text(currentIndex); + }, + onLeave: function(currentIndex, currentRating, $el) { + $(".live-rating").text(currentRating); + }, + callback: function(currentRating, $el) { + volo.cmsKit.public.ratings.ratingPublic.create( + $ratingArea.attr("data-entity-type"), + $ratingArea.attr("data-entity-id"), + { + starCount: parseInt(currentRating) + } + ).then(function () { + widgetManager.refresh($widget); + }) + } + }); + } + + function registerUndoLink() { + console.log("asdf"); + $(".rating-undo-link").on('click', '', function (e) { + e.preventDefault(); + + abp.message.confirm(l("RatingUndoMessage"), function (ok) { + if(ok) { + var id = $(".rating-undo-link").attr("data-id"); + volo.cmsKit.public.ratings.ratingPublic.delete( + id + ).then(function () { + widgetManager.refresh($widget); + }); + } + }) + }); + } + + function init() { + registerCreateOfNewRating(); + registerUndoLink(); + } + + return { + init: init, + getFilters: getFilters + } + }; + + $('.abp-widget-wrapper[data-widget-name="CmsRating"]') + .each(function () { + var widgetManager = new abp.WidgetManager({ + wrapper: $(this), + }); + + widgetManager.init($(this)); + }); + }); +})(jQuery); \ No newline at end of file From 97b3ff113508cb9aede3dbb4bcf7f4f7b418b64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Thu, 27 Aug 2020 15:31:01 +0300 Subject: [PATCH 11/69] added rating style and script contributor --- .../star-rating-svg/css/star-rating-svg.css | 37 +++++++++++++++++++ .../js/jquery.star-rating-svg.min.js | 1 + .../Shared/Components/Rating/Default.cshtml | 31 ++++------------ .../Rating/RatingScriptBundleContributor.cs | 3 ++ .../Rating/RatingStyleBundleContributor.cs | 3 ++ .../Shared/Components/Rating/default.js | 17 +++++++++ 6 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/css/star-rating-svg.css create mode 100644 modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/js/jquery.star-rating-svg.min.js diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/css/star-rating-svg.css b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/css/star-rating-svg.css new file mode 100644 index 0000000000..2c0114687f --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/css/star-rating-svg.css @@ -0,0 +1,37 @@ +.jq-stars { + display: inline-block; +} + +.jq-rating-label { + font-size: 22px; + display: inline-block; + position: relative; + vertical-align: top; + font-family: helvetica, arial, verdana; +} + +.jq-star { + width: 100px; + height: 100px; + display: inline-block; + cursor: pointer; +} + +.jq-star-svg { + width: 100%; + height: 100% ; +} + +.jq-star:hover .fs-star-svg polygon { +} + +.jq-star-svg polygon { + stroke: #000; + stroke-linejoin: round; +} + +/* un-used */ +.jq-shadow { + -webkit-filter: drop-shadow( -2px -2px 2px #888 ); + filter: drop-shadow( -2px -2px 2px #888 ); +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/js/jquery.star-rating-svg.min.js b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/js/jquery.star-rating-svg.min.js new file mode 100644 index 0000000000..df947cfe2b --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/star-rating-svg/js/jquery.star-rating-svg.min.js @@ -0,0 +1 @@ +!function(a){"use strict";var b="starRating",c=function(){},d={totalStars:5,useFullStars:!1,emptyColor:"lightgray",hoverColor:"orange",activeColor:"gold",useGradient:!0,readOnly:!1,disableAfterRate:!0,starGradient:{start:"#FEF7CD",end:"#FF9511"},strokeWidth:0,strokeColor:"black",initialRating:0,starSize:40,callback:c,onHover:c,onLeave:c},e=function(c,e){var f;this.element=c,this.$el=a(c),this.settings=a.extend({},d,e),f=this.$el.data("rating")||this.settings.initialRating,this._state={rating:(Math.round(2*f)/2).toFixed(1)},this._uid=Math.floor(999*Math.random()),e.starGradient||this.settings.useGradient||(this.settings.starGradient.start=this.settings.starGradient.end=this.settings.activeColor),this._defaults=d,this._name=b,this.init()},f={init:function(){this.renderMarkup(),this.addListeners(),this.initRating()},addListeners:function(){this.settings.readOnly||(this.$stars.on("mouseover",this.hoverRating.bind(this)),this.$stars.on("mouseout",this.restoreState.bind(this)),this.$stars.on("click",this.handleRating.bind(this)))},hoverRating:function(a){var b=this.getIndex(a);this.paintStars(b,"hovered"),this.settings.onHover(b+1,this._state.rating,this.$el)},handleRating:function(a){var b=this.getIndex(a),c=b+1;this.applyRating(c,this.$el),this.executeCallback(c,this.$el),this.settings.disableAfterRate&&this.$stars.off()},applyRating:function(a){var b=a-1;this.paintStars(b,"active"),this._state.rating=b+1},restoreState:function(a){var b=this.getIndex(a),c=this._state.rating||-1;this.paintStars(c-1,"active"),this.settings.onLeave(b+1,this._state.rating,this.$el)},getIndex:function(b){var c=a(b.currentTarget),d=c.width(),e=a(b.target).attr("data-side");e=e?e:this.getOffsetByPixel(b,c,d),e=this.settings.useFullStars?"right":e;var f=c.index()-("left"===e?.5:0);return f=.5>f&&b.offsetX=d&&!this.settings.useFullStars?"left":"right"},initRating:function(){this.paintStars(this._state.rating-1,"active")},paintStars:function(b,c){var d,e,f,g;a.each(this.$stars,function(h,i){d=a(i).find('polygon[data-side="left"]'),e=a(i).find('polygon[data-side="right"]'),f=g=b>=h?c:"empty",f=h-b===.5?c:f,d.attr("class","svg-"+f+"-"+this._uid),e.attr("class","svg-"+g+"-"+this._uid)}.bind(this))},renderMarkup:function(){for(var a='
"+this.getLinearGradient(this._uid+"_SVGID_1_",this.settings.emptyColor,this.settings.emptyColor)+this.getLinearGradient(this._uid+"_SVGID_2_",this.settings.hoverColor,this.settings.hoverColor)+this.getLinearGradient(this._uid+"_SVGID_3_",this.settings.starGradient.start,this.settings.starGradient.end)+'
',b="",c=0;c '},executeCallback:function(a,b){var c=this.settings.callback;c(a,b)}},g={unload:function(){var c="plugin_"+b,d=a(this),e=d.data(c).$stars;e.off(),d.removeData(c).remove()},setRating:function(c,d){var e="plugin_"+b,f=a(this),g=f.data(e);c>g.settings.totalStars||0>c||(d&&(c=Math.round(c)),g.applyRating(c))},getRating:function(){var c="plugin_"+b,d=a(this),e=d.data(c);return e._state.rating}};a.extend(e.prototype,f),a.fn[b]=function(c){if(!a.isPlainObject(c)){if(g.hasOwnProperty(c))return g[c].apply(this,Array.prototype.slice.call(arguments,1));a.error("Method "+c+" does not exist on "+b+".js")}return this.each(function(){a.data(this,"plugin_"+b)||a.data(this,"plugin_"+b,new e(this,c))})}}(jQuery,window,document); \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index 7c02d769a5..51b799d2e6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -1,11 +1,18 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Users @using Volo.CmsKit.Localization +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating @inject ICurrentUser CurrentUser @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating.RatingViewModel @inject IHtmlLocalizer L - +@section styles{ + +} + +@section scripts{ + +}
@if (CurrentUser.IsAuthenticated) @@ -43,25 +50,3 @@
} - - - - \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs index a57ff8294f..4340cea485 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingScriptBundleContributor.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.StarRatingSvg; +using Volo.Abp.Modularity; namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating { + [DependsOn(typeof(StarRatingSvgScriptContributor))] public class RatingScriptBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs index dc82f1f5da..bd017f9d1e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingStyleBundleContributor.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.StarRatingSvg; +using Volo.Abp.Modularity; namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating { + [DependsOn(typeof(StarRatingSvgStyleContributor))] public class RatingStyleBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js index 1b29a47190..6bc8dc763e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -1,5 +1,22 @@ (function () { + var authenticated = $(".my-rating-5").attr("data-authenticated"); + $(".my-rating-5").starRating({ + initialRating: 0, + disableAfterRate: true, + useFullStars: true, + readOnly: authenticated === "True", + onHover: function(currentIndex, currentRating, $el) { + $(".live-rating").text(currentIndex); + }, + onLeave: function(currentIndex, currentRating, $el) { + $(".live-rating").text(currentRating); + }, + callback: function(currentRating, $el) { + alert(currentRating); + } + }); + var l = abp.localization.getResource("CmsKit"); $(document).ready(function () { From e96b5bf26b9888bdf2fd73539ef50524d1e82503 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Thu, 27 Aug 2020 18:16:36 +0300 Subject: [PATCH 12/69] CmsKit: Rating widget design --- .../Shared/Components/Rating/Default.cshtml | 66 +++++++++---------- .../Components/Rating/RatingViewComponent.cs | 7 +- .../Shared/Components/Rating/default.css | 45 ++++++++++++- .../Shared/Components/Rating/default.js | 21 +----- 4 files changed, 82 insertions(+), 57 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index 51b799d2e6..f1d96fa97e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -1,52 +1,46 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Users @using Volo.CmsKit.Localization -@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating @inject ICurrentUser CurrentUser @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating.RatingViewModel @inject IHtmlLocalizer L -@section styles{ - -} - -@section scripts{ - -} - -
+
@if (CurrentUser.IsAuthenticated) { -
- - @(Model.CurrentRating != null ? Model.CurrentRating.StarCount : 0) - if (Model.CurrentRating != null) - { - - @L["Undo"] - - } -
-
-

All Stars

- @if (Model.Ratings != null) +
+ + @(Model.CurrentRating != null ? Model.CurrentRating.StarCount + " | " : 0 + "") + @if (Model.CurrentRating != null) { -
    - @foreach (var rating in Model.Ratings) - { -
  • - @rating.StarCount @L["Stars"] - @rating.Count -
  • - } -
+ + @L["Undo"] + }
+ if (Model.Ratings != null) + { +
+ @foreach (var rating in Model.Ratings) + { +
+
@rating.StarCount @L["Stars"]
+
+
+
+
+
+
+
+
@rating.Count
+
+ } +
+ } } else { - +
"> + } -
+
\ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs index 31dedbc532..7c458d033b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -32,6 +33,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating public virtual async Task InvokeAsync(string entityType, string entityId) { var ratings = await RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId); + var totalRating = ratings.Sum(x => x.Count); RatingDto currentUserRating = null; if (CurrentUser.IsAuthenticated) @@ -48,7 +50,8 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating EntityType = entityType, LoginUrl = loginUrl, Ratings = ratings, - CurrentRating = currentUserRating + CurrentRating = currentUserRating, + TotalRating = totalRating }; return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml", viewModel); @@ -66,5 +69,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating public List Ratings { get; set; } public RatingDto CurrentRating { get; set; } + + public int TotalRating { get; set; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css index 5f282702bb..c38f44cace 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.css @@ -1 +1,44 @@ - \ No newline at end of file +.side { + width: 15%; + margin-top:10px; +} + +.middle { + float: left; + width: 70%; + margin-top:10px; + padding-top: 4px; +} + +.row:after { + content: ""; + display: table; + clear: both; +} + +.bar-container { + width: 100%; + background-color: #f1f1f1; + text-align: center; + color: white; +} + +.bar { + height: 18px; +} + +/* Individual bars */ +.bar-5 { background-color: #173F5F; } +.bar-4 { background-color: #20639B; } +.bar-3 { background-color: #3CAEA3; } +.bar-2 { background-color: #F6D55C; } +.bar-1 { background-color: #ED553B; } + +@media (max-width: 400px) { + .side, .middle { + width: 100%; + } + .right { + display: none; + } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js index 6bc8dc763e..2835e57a3c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -1,22 +1,4 @@ (function () { - var authenticated = $(".my-rating-5").attr("data-authenticated"); - - $(".my-rating-5").starRating({ - initialRating: 0, - disableAfterRate: true, - useFullStars: true, - readOnly: authenticated === "True", - onHover: function(currentIndex, currentRating, $el) { - $(".live-rating").text(currentIndex); - }, - onLeave: function(currentIndex, currentRating, $el) { - $(".live-rating").text(currentRating); - }, - callback: function(currentRating, $el) { - alert(currentRating); - } - }); - var l = abp.localization.getResource("CmsKit"); $(document).ready(function () { @@ -32,6 +14,8 @@ } function registerCreateOfNewRating() { + var authenticated = $(".my-rating-5").attr("data-authenticated"); + $(".my-rating-5").starRating({ initialRating: 0, disableAfterRate: true, @@ -58,7 +42,6 @@ } function registerUndoLink() { - console.log("asdf"); $(".rating-undo-link").on('click', '', function (e) { e.preventDefault(); From bebcd1313de64923d54a74e5326a4d9f424971dc Mon Sep 17 00:00:00 2001 From: EngincanV Date: Fri, 28 Aug 2020 09:16:28 +0300 Subject: [PATCH 13/69] CmsKit: Rating missing localizations --- .../Volo/CmsKit/Localization/Resources/en.json | 6 +++++- .../Volo/CmsKit/Localization/Resources/tr.json | 6 +++++- .../Pages/CmsKit/Shared/Components/Rating/Default.cshtml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json index 3e16a17a47..73fd2d52b6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json @@ -13,6 +13,10 @@ "LoginToAddComment": "Login to add comment", "LoginToReply": "Login to reply", "MessageDeletionConfirmationMessage": "This comment will be deleted completely.", - "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display." + "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.", + "Undo": "Undo", + "RatingUndoMessage": "Your rating will be undo.", + "LoginToRate": "Login to rate", + "Star": "Star" } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json index c1664f9122..777e89c979 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json @@ -13,6 +13,10 @@ "LoginToAddComment": "Yorum yapmak için giriş yap", "LoginToReply": "Cevap vermek için giriş yap", "MessageDeletionConfirmationMessage": "Bu yorum tamamen silinecektir", - "CommentAuthorizationExceptionMessage": "Bu yorumları görebilmek için yetki gerekir." + "CommentAuthorizationExceptionMessage": "Bu yorumları görebilmek için yetki gerekir.", + "Undo": "Geri al", + "RatingUndoMessage": "Oylamanız geri alınacak.", + "LoginToRate": "Oylamak için giriş yapın", + "Star": "Yıldız" } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index f1d96fa97e..51c47f93de 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -24,7 +24,7 @@ @foreach (var rating in Model.Ratings) {
-
@rating.StarCount @L["Stars"]
+
@rating.StarCount @L["Star"]
From 2b646ff9d6694b0085c5031e74ec21bb7d6ba97f Mon Sep 17 00:00:00 2001 From: EngincanV Date: Fri, 28 Aug 2020 09:39:08 +0300 Subject: [PATCH 14/69] Update Index.cshtml --- .../cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml index ab628d4412..6fc38ba28b 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml @@ -60,5 +60,8 @@
-@await Component.InvokeAsync(typeof(RatingViewComponent), new { entityType = "quote", entityId = "2" }) +@if (GlobalFeatureManager.Instance.IsEnabled()) +{ + @await Component.InvokeAsync(typeof(RatingViewComponent), new { entityType = "quote", entityId = "2" }) +}
From e0c46f9434b13961570cd0a21285768005bb1643 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Mon, 31 Aug 2020 17:59:52 +0300 Subject: [PATCH 15/69] CmsKit: enhancement after reviewing --- .../CmsKitWebUnifiedModule.cs | 1 + .../Volo/CmsKit/Ratings/RatingConsts.cs | 4 + .../Volo/CmsKit/Ratings/IRatingRepository.cs | 6 -- .../Volo/CmsKit/Ratings/Rating.cs | 2 +- .../CmsKitDbContextModelCreatingExtensions.cs | 2 +- .../CmsKit/Ratings/EfCoreRatingRepository.cs | 12 --- .../MongoDB/Ratings/MongoRatingRepository.cs | 12 --- ...ingInput.cs => CreateUpdateRatingInput.cs} | 4 +- .../Public/Ratings/IRatingPublicAppService.cs | 8 +- .../Public/Ratings/RatingWithStarCountDto.cs | 2 + .../Public/Ratings/UpdateRatingInput.cs | 10 -- .../PublicApplicationAutoMapperProfile.cs | 2 - .../Public/Ratings/RatingPublicAppService.cs | 69 ++++++------ .../Public/Ratings/RatingPublicController.cs | 26 +---- .../Shared/Components/Rating/Default.cshtml | 6 +- .../Components/Rating/RatingViewComponent.cs | 8 +- .../Shared/Components/Rating/default.js | 100 ++++++++++-------- .../Volo.CmsKit.Public.Web.csproj | 4 - .../Ratings/RatingPublicAppService_Tests.cs | 70 ++++++++---- .../Ratings/RatingRepository_Tests.cs | 8 -- 20 files changed, 168 insertions(+), 188 deletions(-) rename modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/{CreateRatingInput.cs => CreateUpdateRatingInput.cs} (63%) delete mode 100644 modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs index 08dadb3dd6..e5d9f23c05 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs index 892b3cef45..27fa9d7584 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs @@ -7,5 +7,9 @@ namespace Volo.CmsKit.Ratings public static int MaxEntityTypeLength { get; set; } = CmsEntityConsts.MaxEntityTypeLength; public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength; + + public static int MaxRating { get; set; } = 5; + + public static int MinRating { get; set; } = 0; } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs index 29ce07df63..a9f71b0dfb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/IRatingRepository.cs @@ -9,12 +9,6 @@ namespace Volo.CmsKit.Ratings { public interface IRatingRepository : IBasicRepository { - Task> GetListAsync( - [NotNull] string entityType, - [NotNull] string entityId, - CancellationToken cancellationToken = default - ); - Task GetCurrentUserRatingAsync( [NotNull] string entityType, [NotNull] string entityId, diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs index 4567b5d275..75f0499db3 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -44,7 +44,7 @@ namespace Volo.CmsKit.Ratings public virtual void SetStarCount(short starCount) { - if(starCount <= 5 && starCount > 0) + if(starCount <= RatingConsts.MaxRating && starCount > RatingConsts.MinRating) { StarCount = starCount; } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index d73f60967a..adda59c3e2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -85,7 +85,7 @@ namespace Volo.CmsKit.EntityFrameworkCore r.Property(x => x.EntityType).IsRequired().HasMaxLength(RatingConsts.MaxEntityTypeLength); r.Property(x => x.EntityId).IsRequired().HasMaxLength(RatingConsts.MaxEntityIdLength); - r.HasIndex(x => new {x.TenantId, x.EntityType, x.EntityId}); + r.HasIndex(x => new {x.TenantId, x.EntityType, x.EntityId, x.CreatorId}); }); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs index 577f2e3a7d..810335b4d6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Ratings/EfCoreRatingRepository.cs @@ -18,18 +18,6 @@ namespace Volo.CmsKit.Ratings { } - public async Task> GetListAsync(string entityType, string entityId, - CancellationToken cancellationToken = default) - { - Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); - Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - - var query = DbSet.Where(r => r.EntityType == entityType && r.EntityId == entityId); - var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); - - return ratings; - } - public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, CancellationToken cancellationToken = default) { diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs index ecf389cde7..92f7f992da 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -19,18 +19,6 @@ namespace Volo.CmsKit.MongoDB.Ratings { } - public async Task> GetListAsync(string entityType, string entityId, - CancellationToken cancellationToken = default) - { - Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); - Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); - - var query = GetMongoQueryable().Where(r => r.EntityType == entityType && r.EntityId == entityId); - var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); - - return ratings; - } - public async Task GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, CancellationToken cancellationToken = default) { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs similarity index 63% rename from modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs rename to modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs index 88b53d5b0c..3018ed8b30 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateRatingInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs @@ -2,9 +2,9 @@ namespace Volo.CmsKit.Public.Ratings { - public class CreateRatingInput + public class CreateUpdateRatingInput { - [Required] + [Required, Range(1, 5)] public short StarCount { get; set; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs index 9e4c8c1f11..aa0aba48b4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/IRatingPublicAppService.cs @@ -8,13 +8,9 @@ namespace Volo.CmsKit.Public.Ratings { public interface IRatingPublicAppService : IApplicationService { - Task> GetListAsync(string entityType, string entityId); - - Task CreateAsync(string entityType, string entityId, CreateRatingInput input); - - Task UpdateAsync(Guid id, UpdateRatingInput input); + Task CreateAsync(string entityType, string entityId, CreateUpdateRatingInput input); - Task DeleteAsync(Guid id); + Task DeleteAsync(string entityType, string entityId); Task GetCurrentUserRatingAsync(string entityType, string entityId); diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs index e8f60ea49b..981ef2bd6e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/RatingWithStarCountDto.cs @@ -5,5 +5,7 @@ public short StarCount { get; set; } public int Count { get; set; } + + public bool IsSelectedByCurrentUser { get; set; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs deleted file mode 100644 index e2f77122d4..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/UpdateRatingInput.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Volo.CmsKit.Public.Ratings -{ - public class UpdateRatingInput - { - [Required] - public short StarCount { get; set; } - } -} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs index 0a0a5403b1..3ac2d1949b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/PublicApplicationAutoMapperProfile.cs @@ -22,8 +22,6 @@ namespace Volo.CmsKit.Public .Ignore(x=> x.Author); CreateMap(); - - CreateMap(); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs index 7621924727..848dbeda28 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Ratings/RatingPublicAppService.cs @@ -22,18 +22,22 @@ namespace Volo.CmsKit.Public.Ratings CmsUserLookupService = cmsUserLookupService; } - public virtual async Task> GetListAsync(string entityType, string entityId) + [Authorize] + public virtual async Task CreateAsync(string entityType, string entityId, + CreateUpdateRatingInput input) { - var ratings = await RatingRepository.GetListAsync(entityType, entityId); - var ratingDto = ObjectMapper.Map, List>(ratings); + var userId = CurrentUser.GetId(); + var user = await CmsUserLookupService.GetByIdAsync(userId); - return new ListResultDto(ratingDto); - } + var currentUserRating = await RatingRepository.GetCurrentUserRatingAsync(entityType, entityId, userId); - [Authorize] - public virtual async Task CreateAsync(string entityType, string entityId, CreateRatingInput input) - { - var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId()); + if (currentUserRating != null) + { + currentUserRating.SetStarCount(input.StarCount); + var updatedRating = await RatingRepository.UpdateAsync(currentUserRating); + + return ObjectMapper.Map(updatedRating); + } var rating = await RatingRepository.InsertAsync( new Rating( @@ -50,50 +54,51 @@ namespace Volo.CmsKit.Public.Ratings } [Authorize] - public virtual async Task UpdateAsync(Guid id, UpdateRatingInput input) - { - var rating = await RatingRepository.GetAsync(id); - - if (rating.CreatorId != CurrentUser.GetId()) - { - throw new AbpAuthorizationException(); - } - - rating.SetStarCount(input.StarCount); - - var updatedRating = await RatingRepository.UpdateAsync(rating); - - return ObjectMapper.Map(updatedRating); - } - - [Authorize] - public virtual async Task DeleteAsync(Guid id) + public virtual async Task DeleteAsync(string entityType, string entityId) { - var rating = await RatingRepository.GetAsync(id); + var rating = await RatingRepository.GetCurrentUserRatingAsync(entityType, entityId, CurrentUser.GetId()); if (rating.CreatorId != CurrentUser.GetId()) { throw new AbpAuthorizationException(); } - await RatingRepository.DeleteAsync(id); + await RatingRepository.DeleteAsync(rating.Id); } [Authorize] public virtual async Task GetCurrentUserRatingAsync(string entityType, string entityId) { var currentUserId = CurrentUser.GetId(); - + var rating = await RatingRepository.GetCurrentUserRatingAsync(entityType, entityId, currentUserId); return ObjectMapper.Map(rating); } - public virtual async Task> GetGroupedStarCountsAsync(string entityType, string entityId) + public virtual async Task> GetGroupedStarCountsAsync(string entityType, + string entityId) { var ratings = await RatingRepository.GetGroupedStarCountsAsync(entityType, entityId); - return ObjectMapper.Map, List>(ratings); + var userRatingOrNull = CurrentUser.IsAuthenticated + ? await RatingRepository.GetCurrentUserRatingAsync(entityType, entityId, CurrentUser.GetId()) + : null; + + var ratingWithStarCountDto = new List(); + + foreach (var rating in ratings) + { + ratingWithStarCountDto.Add( + new RatingWithStarCountDto + { + StarCount = rating.StarCount, + Count = rating.Count, + IsSelectedByCurrentUser = userRatingOrNull != null && userRatingOrNull.StarCount == rating.StarCount + }); + } + + return ratingWithStarCountDto; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs index 25cc40ea5a..e136c4564d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Ratings/RatingPublicController.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp; -using Volo.Abp.Application.Dtos; using Volo.Abp.GlobalFeatures; using Volo.CmsKit.GlobalFeatures; @@ -22,32 +20,18 @@ namespace Volo.CmsKit.Public.Ratings RatingPublicAppService = ratingPublicAppService; } - [HttpGet] - [Route("{entityType}/{entityId}")] - public virtual Task> GetListAsync(string entityType, string entityId) - { - return RatingPublicAppService.GetListAsync(entityType, entityId); - } - [HttpPut] [Route("{entityType}/{entityId}")] - public virtual Task CreateAsync(string entityType, string entityId, CreateRatingInput input) + public virtual Task CreateAsync(string entityType, string entityId, CreateUpdateRatingInput input) { return RatingPublicAppService.CreateAsync(entityType, entityId, input); } - [HttpPut] - [Route("{id}")] - public virtual Task UpdateAsync(Guid id, UpdateRatingInput input) - { - return RatingPublicAppService.UpdateAsync(id, input); - } - [HttpDelete] - [Route("{id}")] - public virtual Task DeleteAsync(Guid id) + [Route("{entityType}/{entityId}")] + public virtual Task DeleteAsync(string entityType, string entityId) { - return RatingPublicAppService.DeleteAsync(id); + return RatingPublicAppService.DeleteAsync(entityType, entityId); } [HttpGet] diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index 51c47f93de..b5cfbbfaa9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -9,11 +9,11 @@ @if (CurrentUser.IsAuthenticated) {
- - @(Model.CurrentRating != null ? Model.CurrentRating.StarCount + " | " : 0 + "") + + @(Model.CurrentRating != null ? Model.CurrentRating + " | " : 0 + "") @if (Model.CurrentRating != null) { - + @L["Undo"] } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs index 7c458d033b..aabbd3c286 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -34,11 +34,11 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating { var ratings = await RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId); var totalRating = ratings.Sum(x => x.Count); - - RatingDto currentUserRating = null; + + short? currentUserRating = null; if (CurrentUser.IsAuthenticated) { - currentUserRating = await RatingPublicAppService.GetCurrentUserRatingAsync(entityType, entityId); + currentUserRating = ratings.Find(x => x.IsSelectedByCurrentUser)?.StarCount; } var loginUrl = @@ -68,7 +68,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating public List Ratings { get; set; } - public RatingDto CurrentRating { get; set; } + public short? CurrentRating { get; set; } public int TotalRating { get; set; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js index 2835e57a3c..4416d7fb48 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -1,81 +1,89 @@ (function () { var l = abp.localization.getResource("CmsKit"); - + $(document).ready(function () { abp.widgets.CmsRating = function ($widget) { var widgetManager = $widget.data("abp-widget-manager"); var $ratingArea = $widget.find(".cms-rating-area"); - + function getFilters() { return { entityType: $ratingArea.attr("data-entity-type"), entityId: $ratingArea.attr("data-entity-id") }; } - + function registerCreateOfNewRating() { - var authenticated = $(".my-rating-5").attr("data-authenticated"); + $widget.find(".my-rating-5").each(function () { + var authenticated = $(this).attr("data-authenticated"); - $(".my-rating-5").starRating({ - initialRating: 0, - disableAfterRate: true, - useFullStars: true, - readOnly: authenticated === "True", - onHover: function(currentIndex, currentRating, $el) { - $(".live-rating").text(currentIndex); - }, - onLeave: function(currentIndex, currentRating, $el) { - $(".live-rating").text(currentRating); - }, - callback: function(currentRating, $el) { - volo.cmsKit.public.ratings.ratingPublic.create( - $ratingArea.attr("data-entity-type"), - $ratingArea.attr("data-entity-id"), - { - starCount: parseInt(currentRating) + $(this).starRating({ + initialRating: 0, + disableAfterRate: true, + useFullStars: true, + readOnly: authenticated === "True", + onHover: function (currentIndex, currentRating, $el) { + $widget.find(".live-rating").text(currentIndex); + }, + onLeave: function (currentIndex, currentRating, $el) { + $widget.find(".live-rating").text(currentRating); + }, + callback: function (currentRating, $el) { + volo.cmsKit.public.ratings.ratingPublic.create( + $ratingArea.attr("data-entity-type"), + $ratingArea.attr("data-entity-id"), + { + starCount: parseInt(currentRating) + } + ).then(function () { + widgetManager.refresh($widget); + }) } - ).then(function () { - widgetManager.refresh($widget); - }) + }); } - }); + ); } - + function registerUndoLink() { - $(".rating-undo-link").on('click', '', function (e) { - e.preventDefault(); - - abp.message.confirm(l("RatingUndoMessage"), function (ok) { - if(ok) { - var id = $(".rating-undo-link").attr("data-id"); - volo.cmsKit.public.ratings.ratingPublic.delete( - id - ).then(function () { - widgetManager.refresh($widget); - }); - } - }) + $widget.find(".rating-undo-link").each(function () { + $(this).on('click', '', function (e) { + e.preventDefault(); + + abp.message.confirm(l("RatingUndoMessage"), function (ok) { + if (ok) { + volo.cmsKit.public.ratings.ratingPublic.delete( + $ratingArea.attr("data-entity-type"), + $ratingArea.attr("data-entity-id") + ).then(function () { + widgetManager.refresh($widget); + }); + } + }) + }); }); } - + function init() { registerCreateOfNewRating(); registerUndoLink(); } - + return { init: init, getFilters: getFilters } - }; - + } + ; + $('.abp-widget-wrapper[data-widget-name="CmsRating"]') .each(function () { var widgetManager = new abp.WidgetManager({ wrapper: $(this), }); - + widgetManager.init($(this)); }); - }); -})(jQuery); \ No newline at end of file + }) + ; +}) +(jQuery); \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj index 3d52f544e0..6d2f8ff303 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj @@ -27,10 +27,6 @@ - - true - PreserveNewest - diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs index 1a21da8425..c8f2acd950 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Ratings/RatingPublicAppService_Tests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Shouldly; @@ -34,8 +35,8 @@ namespace Volo.CmsKit.Ratings var newRating = await _ratingAppService.CreateAsync( _cmsKitTestData.EntityType1, - _cmsKitTestData.EntityId1, - new CreateRatingInput + _cmsKitTestData.EntityId2, + new CreateUpdateRatingInput { StarCount = 4 }); @@ -44,53 +45,86 @@ namespace Volo.CmsKit.Ratings { var ratings = context.Set().Where(x => x.EntityId == _cmsKitTestData.EntityId1 && x.EntityType == _cmsKitTestData.EntityType1).ToList(); - + ratings - .Any(c => c.Id == newRating.Id && c.CreatorId == newRating.CreatorId && c.StarCount == newRating.StarCount) + .Any(c => c.Id == newRating.Id && c.CreatorId == newRating.CreatorId && + c.StarCount == newRating.StarCount) .ShouldBeTrue(); }); } [Fact] - public async Task UpdateAsync() + public async Task CreateAsync_Should_Update_If_Rating_Is_Exist() { _currentUser.Id.Returns(_cmsKitTestData.User1Id); - var rating = await _ratingAppService.CreateAsync( + var entity = + await _ratingAppService.GetCurrentUserRatingAsync(_cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1); + + var updatedEntity = await _ratingAppService.CreateAsync( _cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1, - new CreateRatingInput + new CreateUpdateRatingInput { - StarCount = 4 + StarCount = 5 }); - await _ratingAppService.UpdateAsync(rating.Id, new UpdateRatingInput - { - StarCount = 5 - }); + entity.Id.ShouldBe(updatedEntity.Id); + entity.EntityId.ShouldBe(updatedEntity.EntityId); + entity.EntityType.ShouldBe(updatedEntity.EntityType); + entity.StarCount.ShouldBe(updatedEntity.StarCount); + } + + [Fact] + public async Task GetCurrentUserRatingAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var rating = await _ratingAppService.GetCurrentUserRatingAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1 + ); UsingDbContext(context => { - var updatedRating = context.Set().Single(x => x.Id == rating.Id); - - updatedRating.StarCount.ShouldBe((short)5); + var ratings = context.Set().Where(x => + x.EntityId == _cmsKitTestData.EntityId1 && x.EntityType == _cmsKitTestData.EntityType1).ToList(); + + ratings + .Any(c => c.Id == rating.Id && c.EntityId == rating.EntityId && c.EntityType == rating.EntityType) + .ShouldBeTrue(); }); } + [Fact] + public async Task GetGroupedStarCountsAsync() + { + _currentUser.Id.Returns(_cmsKitTestData.User1Id); + + var ratings = await _ratingAppService.GetGroupedStarCountsAsync( + _cmsKitTestData.EntityType1, + _cmsKitTestData.EntityId1 + ); + + ratings.ShouldNotBeNull(); + ratings.Count.ShouldBeGreaterThan(0); + } + [Fact] public async Task DeleteAsync() { _currentUser.Id.Returns(_cmsKitTestData.User1Id); - + var rating = await _ratingAppService.CreateAsync( _cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1, - new CreateRatingInput + new CreateUpdateRatingInput { StarCount = 4 }); - await _ratingAppService.DeleteAsync(rating.Id); + await _ratingAppService.DeleteAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); UsingDbContext(context => { diff --git a/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs index b09b380f8e..19da73cf50 100644 --- a/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.TestBase/Ratings/RatingRepository_Tests.cs @@ -17,14 +17,6 @@ namespace Volo.CmsKit.Ratings _ratingRepository = GetRequiredService(); } - [Fact] - public async Task GetListAsync() - { - var list = await _ratingRepository.GetListAsync(_cmsKitTestData.EntityType1, _cmsKitTestData.EntityId1); - - list.Count.ShouldBeGreaterThan(0); - } - [Fact] public async Task GetCurrentUserRatingAsync() { From 905ad6311b4b89651dea6689e77611140552d2cb Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:04:53 +0800 Subject: [PATCH 16/69] Replace the obsolete AddIdentityServerAuthentication method. Resolve #5179 --- .../MyCompanyName.MyProjectName.HttpApi.Host.csproj | 2 +- .../MyProjectNameHttpApiHostModule.cs | 9 +++++---- ...yCompanyName.MyProjectName.HttpApi.HostWithIds.csproj | 1 - .../MyProjectNameHttpApiHostModule.cs | 6 +++--- .../MyCompanyName.MyProjectName.Web.csproj | 1 - .../MyProjectNameWebModule.cs | 4 ++-- .../src/MyCompanyName.MyProjectName.Web/appsettings.json | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 71b248c32e..e497fa9a69 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -14,7 +14,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 3771a1874d..e71450209f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; @@ -88,12 +89,12 @@ namespace MyCompanyName.MyProjectName private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { - context.Services.AddAuthentication("Bearer") - .AddIdentityServerAuthentication(options => + context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = true; - options.ApiName = "MyProjectName"; + options.RequireHttpsMetadata = false; + options.Audience = "MyProjectName"; }); } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index 373d19dcfb..45b4e0517b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -14,7 +14,6 @@ - diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index 95a26d9084..1cd8ea1c0b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -92,12 +92,12 @@ namespace MyCompanyName.MyProjectName private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = "MyProjectName"; - options.JwtBackChannelHandler = new HttpClientHandler() + options.Audience = "MyProjectName"; + options.BackchannelHttpHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index eb374cb80e..961e8e33d4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -36,7 +36,6 @@ - diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index cf201f90cc..8f63e0768f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -94,11 +94,11 @@ namespace MyCompanyName.MyProjectName.Web private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = "MyProjectName"; + options.Audience = "MyProjectName"; }); } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/appsettings.json index 99a24b4a2d..6cfe8948fa 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/appsettings.json @@ -6,7 +6,7 @@ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true" }, "AuthServer": { - "Authority": "https://localhost:44301" + "Authority": "https://localhost:44303" }, "IdentityServer": { "Clients": { From f0799b4d83f5a5f97dd275b694fae22918877510 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:06:21 +0800 Subject: [PATCH 17/69] RequireHttpsMetadata true. --- .../MyProjectNameHttpApiHostModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index e71450209f..b217a0ada7 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -93,7 +93,7 @@ namespace MyCompanyName.MyProjectName .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = false; + options.RequireHttpsMetadata = true; options.Audience = "MyProjectName"; }); } From 1a6c14ee8b0f0e8c6bfa479a0519792d1affc7db Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:15:33 +0800 Subject: [PATCH 18/69] Replace AddIdentityServerAuthentication in app module template. --- .../MyCompanyName.MyProjectName.HttpApi.Host.csproj | 3 ++- .../MyProjectNameHttpApiHostModule.cs | 9 +++++---- .../MyCompanyName.MyProjectName.IdentityServer.csproj | 1 - .../MyProjectNameIdentityServerModule.cs | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 35e73d4868..8e5e31fcc6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -13,7 +13,8 @@ - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 6d5bf1a7c8..0145f8a41a 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.Claims; using IdentityModel; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; @@ -103,12 +104,12 @@ namespace MyCompanyName.MyProjectName AbpClaimTypes.Role = JwtClaimTypes.Role; AbpClaimTypes.Email = JwtClaimTypes.Email; - context.Services.AddAuthentication("Bearer") - .AddIdentityServerAuthentication(options => + context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = "MyProjectName"; + options.Audience = "MyProjectName"; }); Configure(options => @@ -163,7 +164,7 @@ namespace MyCompanyName.MyProjectName app.UseCorrelationId(); app.UseVirtualFiles(); app.UseRouting(); - app.UseCors(DefaultCorsPolicyName); + app.UseCors(DefaultCorsPolicyName); app.UseAuthentication(); app.UseAbpClaimsMap(); if (MultiTenancyConsts.IsEnabled) diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj index c70808deed..3b18a90d7e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj @@ -11,7 +11,6 @@ - diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs index cfda73627d..2eed1ad3cb 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs @@ -118,11 +118,11 @@ namespace MyCompanyName.MyProjectName }); context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = configuration["AuthServer:ApiName"]; + options.Audience = configuration["AuthServer:ApiName"]; }); Configure(options => @@ -182,10 +182,10 @@ namespace MyCompanyName.MyProjectName app.UseCorrelationId(); app.UseVirtualFiles(); app.UseRouting(); - app.UseCors(DefaultCorsPolicyName); + app.UseCors(DefaultCorsPolicyName); app.UseAuthentication(); app.UseJwtTokenMiddleware(); - + if (MultiTenancyConsts.IsEnabled) { app.UseMultiTenancy(); From be0ac62f5cbf5a209ecb80450085d281fc35c40b Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:33:18 +0800 Subject: [PATCH 19/69] Update cms-kit module. --- .../Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs | 7 ++++--- .../Volo.CmsKit.HttpApi.Host.csproj | 3 ++- .../CmsKitIdentityServerModule.cs | 4 ++-- .../Volo.CmsKit.IdentityServer.csproj | 1 - 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs index f1a9b8568f..fe9df5d7b6 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.Claims; using IdentityModel; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; @@ -108,12 +109,12 @@ namespace Volo.CmsKit AbpClaimTypes.Role = JwtClaimTypes.Role; AbpClaimTypes.Email = JwtClaimTypes.Email; - context.Services.AddAuthentication("Bearer") - .AddIdentityServerAuthentication(options => + context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = "CmsKit"; + options.Audience = "CmsKit"; }); Configure(options => diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj index 9fa7c4be20..99b2d0f1c0 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj @@ -13,7 +13,8 @@ - + + diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs index b9afe934a3..5fbff7cfeb 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs @@ -123,11 +123,11 @@ namespace Volo.CmsKit }); context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => + .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; - options.ApiName = configuration["AuthServer:ApiName"]; + options.Audience = configuration["AuthServer:ApiName"]; }); Configure(options => diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj index b26c2bbe06..a32a6c2dc3 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj @@ -11,7 +11,6 @@ - From a542a88a8047ebaea3aeaa81e302a6a2859f6b7b Mon Sep 17 00:00:00 2001 From: EngincanV Date: Tue, 1 Sep 2020 12:16:46 +0300 Subject: [PATCH 20/69] CmsKit: last changes after review --- .../Volo/CmsKit/Ratings/RatingConsts.cs | 4 ++-- .../src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs | 4 ++-- .../Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs | 3 ++- .../Pages/CmsKit/Shared/Components/Rating/Default.cshtml | 2 +- .../CmsKit/Shared/Components/Rating/RatingViewComponent.cs | 2 +- .../Pages/CmsKit/Shared/Components/Rating/default.js | 6 ++---- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs index 27fa9d7584..d52bec599b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs @@ -8,8 +8,8 @@ namespace Volo.CmsKit.Ratings public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength; - public static int MaxRating { get; set; } = 5; + public const int MaxStarCount = 5; - public static int MinRating { get; set; } = 0; + public const int MinStarCount = 1; } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs index 75f0499db3..a3575cf0b4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Ratings/Rating.cs @@ -44,13 +44,13 @@ namespace Volo.CmsKit.Ratings public virtual void SetStarCount(short starCount) { - if(starCount <= RatingConsts.MaxRating && starCount > RatingConsts.MinRating) + if(starCount <= RatingConsts.MaxStarCount && starCount >= RatingConsts.MinStarCount) { StarCount = starCount; } else { - throw new ArgumentOutOfRangeException("Choosen star must between 1 and 5"); + throw new ArgumentOutOfRangeException($"Choosen star must between {RatingConsts.MinStarCount} and {RatingConsts.MaxStarCount}"); } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs index 3018ed8b30..c6fa01b7d4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; +using Volo.CmsKit.Ratings; namespace Volo.CmsKit.Public.Ratings { public class CreateUpdateRatingInput { - [Required, Range(1, 5)] + [Required, Range(RatingConsts.MinStarCount, RatingConsts.MaxStarCount)] public short StarCount { get; set; } } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml index b5cfbbfaa9..82397e49c4 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml @@ -5,7 +5,7 @@ @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating.RatingViewModel @inject IHtmlLocalizer L -
+
@if (CurrentUser.IsAuthenticated) {
diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs index aabbd3c286..cae050ea40 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs @@ -42,7 +42,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating } var loginUrl = - $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}"; + $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-rating_{entityType}_{entityId}"; var viewModel = new RatingViewModel { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js index 4416d7fb48..431fd7884b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js @@ -72,8 +72,7 @@ init: init, getFilters: getFilters } - } - ; + }; $('.abp-widget-wrapper[data-widget-name="CmsRating"]') .each(function () { @@ -83,7 +82,6 @@ widgetManager.init($(this)); }); - }) - ; + }); }) (jQuery); \ No newline at end of file From a3685d4d5bb481123598ec4b39e8f38d8a7b5f47 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 1 Sep 2020 15:58:33 +0300 Subject: [PATCH 21/69] Allow to add new tabs to the account profile manage page --- .../Account/Localization/Resources/en.json | 4 +- .../Account/Localization/Resources/tr.json | 4 +- .../AbpAccountWebAutomapperProfile.cs | 3 +- .../AbpAccountWebModule.cs | 31 +++++++- ...ilePasswordManagementGroupViewComponent.cs | 59 +++++++++++++++ .../Password/Default.cshtml | 17 +++++ .../Password/Default.js | 31 ++++++++ ...ersonalInfoManagementGroupViewComponent.cs | 56 ++++++++++++++ .../PersonalInfo/Default.cshtml | 39 ++++++++++ .../PersonalInfo/Default.js | 19 +++++ .../Pages/Account/Manage.cshtml | 73 +++++-------------- .../Pages/Account/Manage.cshtml.cs | 72 +++--------------- .../Pages/Account/Manage.js | 47 ------------ .../Pages/Account/Register.cshtml.cs | 1 - ...AccountProfileManagementPageContributor.cs | 48 ++++++++++++ .../IProfileManagementPageContributor.cs | 9 +++ .../ProfileManagementPageCreationContext.cs | 19 +++++ .../ProfileManagementPageGroup.cs | 39 ++++++++++ .../ProfileManagementPageOptions.cs | 14 ++++ 19 files changed, 417 insertions(+), 168 deletions(-) create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js delete mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js create mode 100644 modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json index e9215d3f5a..85bd040d8c 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json @@ -53,6 +53,8 @@ "ResetPassword_Information": "Please enter your new password.", "YourPasswordIsSuccessfullyReset": "Your password is successfully reset.", "GoToTheApplication": "Go to the application", - "BackToLogin": "Back to login" + "BackToLogin": "Back to login", + "ProfileTab:Password": "Change password", + "ProfileTab:PersonalInfo": "Personal info" } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json index bbb2ac3638..c9aff72675 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json @@ -52,6 +52,8 @@ "ResetPassword_Information": "Lütfen yeni şifrenizi belirleyin.", "YourPasswordIsSuccessfullyReset": "Şifreniz başarıyla sıfırlandı.", "GoToTheApplication": "Uygulamaya git", - "BackToLogin": "Girişe dön" + "BackToLogin": "Girişe dön", + "ProfileTab:Password": "Şifre değiştir", + "ProfileTab:PersonalInfo": "Kişisel bilgiler" } } diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs index ab7ea41269..6204c20582 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs @@ -1,6 +1,7 @@ using Volo.Abp.Account.Web.Pages.Account; using Volo.Abp.Identity; using AutoMapper; +using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; namespace Volo.Abp.Account.Web { @@ -8,7 +9,7 @@ namespace Volo.Abp.Account.Web { public AbpAccountWebAutoMapperProfile() { - CreateMap(); + CreateMap(); } } } diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index 10886e4c17..303963d24f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.Account.Web.ProfileManagement; using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AutoMapper; @@ -50,16 +53,38 @@ namespace Volo.Abp.Account.Web options.Contributors.Add(new AccountModuleToolbarContributor()); }); + ConfigureProfileManagementPage(); + + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddProfile(validate: true); + }); + } + + private void ConfigureProfileManagementPage() + { Configure(options => { options.Conventions.AuthorizePage("/Account/Manage"); }); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => + Configure(options => { - options.AddProfile(validate: true); + options.Contributors.Add(new AccountProfileManagementPageContributor()); }); + + Configure(options => + { + options.ScriptBundles + .Configure(typeof(ManageModel).FullName, + configuration => + { + configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Password/Default.js"); + configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js"); + }); + }); + } } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs new file mode 100644 index 0000000000..7a0df6878b --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Auditing; +using Volo.Abp.Identity; +using Volo.Abp.Validation; + +namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password +{ + public class AccountProfilePasswordManagementGroupViewComponent : AbpViewComponent + { + private readonly IProfileAppService _profileAppService; + + public AccountProfilePasswordManagementGroupViewComponent( + IProfileAppService profileAppService) + { + _profileAppService = profileAppService; + } + + public async Task InvokeAsync() + { + var user = await _profileAppService.GetAsync(); + + var model = new ChangePasswordInfoModel + { + HideOldPasswordInput = !user.HasPassword + }; + + return View("~/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml", model); + } + + public class ChangePasswordInfoModel + { + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] + [Display(Name = "DisplayName:CurrentPassword")] + [DataType(DataType.Password)] + [DisableAuditing] + public string CurrentPassword { get; set; } + + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] + [Display(Name = "DisplayName:NewPassword")] + [DataType(DataType.Password)] + [DisableAuditing] + public string NewPassword { get; set; } + + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] + [Display(Name = "DisplayName:NewPasswordConfirm")] + [DataType(DataType.Password)] + [DisableAuditing] + public string NewPasswordConfirm { get; set; } + + public bool HideOldPasswordInput { get; set; } + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml new file mode 100644 index 0000000000..febab6d081 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml @@ -0,0 +1,17 @@ +@using Volo.Abp.Account.Localization +@using Volo.Abp.Users +@using Microsoft.AspNetCore.Mvc.Localization +@inject IHtmlLocalizer L +@inject ICurrentUser CurrentUser +@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password.AccountProfilePasswordManagementGroupViewComponent.ChangePasswordInfoModel + +

@L["ChangePassword"]


+
+ @if (!Model.HideOldPasswordInput) + { + + } + + + + diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js new file mode 100644 index 0000000000..ef38f68466 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/Default.js @@ -0,0 +1,31 @@ +(function ($) { + $(function () { + var l = abp.localization.getResource("AbpAccount"); + + $('#ChangePasswordForm').submit(function (e) { + e.preventDefault(); + + if (!$('#ChangePasswordForm').valid()) { + return false; + } + + var input = $('#ChangePasswordForm').serializeFormToObject(); + + if ( + input.newPassword != input.newPasswordConfirm || + input.newPassword == '' + ) { + abp.message.error(l('NewPasswordConfirmFailed')); + return; + } + + if (input.currentPassword && input.currentPassword == ''){ + return; + } + + volo.abp.identity.profile.changePassword(input).then(function (result) { + abp.message.success(l('PasswordChanged')); + }); + }); + }); +})(jQuery); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs new file mode 100644 index 0000000000..512d646ff2 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs @@ -0,0 +1,56 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Identity; +using Volo.Abp.Validation; + +namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo +{ + public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewComponent + { + private readonly IProfileAppService _profileAppService; + + public AccountProfilePersonalInfoManagementGroupViewComponent( + IProfileAppService profileAppService) + { + _profileAppService = profileAppService; + + ObjectMapperContext = typeof(AbpAccountWebModule); + } + + public async Task InvokeAsync() + { + var user = await _profileAppService.GetAsync(); + + var model = ObjectMapper.Map(user); + + return View("~/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml", model); + } + + public class PersonalInfoModel + { + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] + [Display(Name = "DisplayName:UserName")] + public string UserName { get; set; } + + [Required] + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] + [Display(Name = "DisplayName:Email")] + public string Email { get; set; } + + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] + [Display(Name = "DisplayName:Name")] + public string Name { get; set; } + + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] + [Display(Name = "DisplayName:Surname")] + public string Surname { get; set; } + + [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] + [Display(Name = "DisplayName:PhoneNumber")] + public string PhoneNumber { get; set; } + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml new file mode 100644 index 0000000000..33ca670b0c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml @@ -0,0 +1,39 @@ +@using Volo.Abp.Account.Localization +@using Volo.Abp.Users +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Abp.Identity.Settings +@using Volo.Abp.Settings +@inject IHtmlLocalizer L +@inject ICurrentUser CurrentUser +@inject ISettingProvider SettingManager +@inject IThemeManager ThemeManager +@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel +@{ + var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true", + StringComparison.OrdinalIgnoreCase); + + var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true", + StringComparison.OrdinalIgnoreCase); +} + +

@L["PersonalSettings"]


+
+ + + + + + + + + + + + + + + + + + diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js new file mode 100644 index 0000000000..6f5642d07d --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js @@ -0,0 +1,19 @@ +(function ($) { + $(function () { + var l = abp.localization.getResource("AbpAccount"); + + $('#PersonalSettingsForm').submit(function (e) { + e.preventDefault(); + + if (!$('#PersonalSettingsForm').valid()) { + return false; + } + + var input = $('#PersonalSettingsForm').serializeFormToObject(); + + volo.abp.identity.profile.update(input).then(function (result) { + abp.notify.success(l('PersonalSettingsSaved')); + }); + }); + }); +})(jQuery); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml index 23350442b2..ff6262f3ec 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml @@ -2,66 +2,33 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Web.Pages.Account -@using Volo.Abp.Identity.Settings -@using Volo.Abp.Settings @using Volo.Abp.AspNetCore.Mvc.UI.Theming -@inject ISettingProvider SettingManager @inject IThemeManager ThemeManager @inject IHtmlLocalizer L @model ManageModel @{ Layout = ThemeManager.CurrentTheme.GetApplicationLayout(); - var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true", - StringComparison.OrdinalIgnoreCase); - - var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true", - StringComparison.OrdinalIgnoreCase); } @section scripts { - - - + } - - - - @if (!Model.DisablePasswordChange) - { - -

@L["ChangePassword"].Value


-
- @if (!Model.HideOldPasswordInput) - { - - } - - - - -
- } - -

@L["PersonalSettings"].Value


-
- - - - - - - - - - - - - - - - - -
-
-
-
+
+ + + + @foreach (var group in Model.ProfileManagementPageCreationContext.Groups) + { + +

@group.DisplayName

+
+ @await Component.InvokeAsync(group.ComponentType, new + { + parameter = group.Parameter + }) +
+ } +
+
+
+
diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs index db51f9890c..f7b08eb6c4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs @@ -2,35 +2,31 @@ using System.Threading.Tasks; using Volo.Abp.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.Account.Web.ProfileManagement; using Volo.Abp.Validation; namespace Volo.Abp.Account.Web.Pages.Account { public class ManageModel : AccountPageModel { - public ChangePasswordInfoModel ChangePasswordInfoModel { get; set; } + public ProfileManagementPageCreationContext ProfileManagementPageCreationContext { get; private set; } - public PersonalSettingsInfoModel PersonalSettingsInfoModel { get; set; } + protected ProfileManagementPageOptions Options { get; } - public bool DisablePasswordChange { get; set; } - - public bool HideOldPasswordInput { get; set; } - - protected IProfileAppService ProfileAppService { get; } - - public ManageModel(IProfileAppService profileAppService) + public ManageModel(IOptions options) { - ProfileAppService = profileAppService; + Options = options.Value; } public virtual async Task OnGetAsync() { - var user = await ProfileAppService.GetAsync(); - - PersonalSettingsInfoModel = ObjectMapper.Map(user); + ProfileManagementPageCreationContext = new ProfileManagementPageCreationContext(ServiceProvider); - DisablePasswordChange = user.IsExternal; - HideOldPasswordInput = !user.HasPassword; + foreach (var contributor in Options.Contributors) + { + await contributor.ConfigureAsync(ProfileManagementPageCreationContext); + } return Page(); } @@ -40,50 +36,4 @@ namespace Volo.Abp.Account.Web.Pages.Account return Task.FromResult(Page()); } } - - public class ChangePasswordInfoModel - { - [Required] - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] - [Display(Name = "DisplayName:CurrentPassword")] - [DataType(DataType.Password)] - public string CurrentPassword { get; set; } - - [Required] - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] - [Display(Name = "DisplayName:NewPassword")] - [DataType(DataType.Password)] - public string NewPassword { get; set; } - - [Required] - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] - [Display(Name = "DisplayName:NewPasswordConfirm")] - [DataType(DataType.Password)] - public string NewPasswordConfirm { get; set; } - } - - public class PersonalSettingsInfoModel - { - [Required] - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] - [Display(Name = "DisplayName:UserName")] - public string UserName { get; set; } - - [Required] - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] - [Display(Name = "DisplayName:Email")] - public string Email { get; set; } - - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] - [Display(Name = "DisplayName:Name")] - public string Name { get; set; } - - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] - [Display(Name = "DisplayName:Surname")] - public string Surname { get; set; } - - [DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] - [Display(Name = "DisplayName:PhoneNumber")] - public string PhoneNumber { get; set; } - } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js deleted file mode 100644 index 3c7c4148e8..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js +++ /dev/null @@ -1,47 +0,0 @@ -(function ($) { - var l = abp.localization.getResource('AbpAccount'); - - var _profileService = volo.abp.identity.profile; - - $('#ChangePasswordForm').submit(function (e) { - e.preventDefault(); - - if (!$('#ChangePasswordForm').valid()) { - return false; - } - - var input = $('#ChangePasswordForm').serializeFormToObject() - .changePasswordInfoModel; - - if ( - input.newPassword != input.newPasswordConfirm || - input.newPassword == '' - ) { - abp.message.error(l('NewPasswordConfirmFailed')); - return; - } - - if (input.currentPassword && input.currentPassword == ''){ - return; - } - - _profileService.changePassword(input).then(function (result) { - abp.message.success(l('PasswordChanged')); - }); - }); - - $('#PersonalSettingsForm').submit(function (e) { - e.preventDefault(); - - if (!$('#PersonalSettingsForm').valid()) { - return false; - } - - var input = $('#PersonalSettingsForm').serializeFormToObject() - .personalSettingsInfoModel; - - _profileService.update(input).then(function (result) { - abp.notify.success(l('PersonalSettingsSaved')); - }); - }); -})(jQuery); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index a8ec942084..0560197cd3 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -18,7 +18,6 @@ namespace Volo.Abp.Account.Web.Pages.Account { public class RegisterModel : AccountPageModel { - protected IAccountAppService AccountAppService { get; } [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } diff --git a/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs new file mode 100644 index 0000000000..3f2a11ac86 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/AccountProfileManagementPageContributor.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password; +using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; +using Volo.Abp.Identity; +using Volo.Abp.Users; + +namespace Volo.Abp.Account.Web.ProfileManagement +{ + public class AccountProfileManagementPageContributor : IProfileManagementPageContributor + { + public async Task ConfigureAsync(ProfileManagementPageCreationContext context) + { + var l = context.ServiceProvider.GetRequiredService>(); + + if (await IsPasswordChangeEnabled(context)) + { + context.Groups.Add( + new ProfileManagementPageGroup( + "Volo.Abp.Account.Password", + l["ProfileTab:Password"], + typeof(AccountProfilePasswordManagementGroupViewComponent) + ) + ); + } + + context.Groups.Add( + new ProfileManagementPageGroup( + "Volo.Abp.Account.PersonalInfo", + l["ProfileTab:PersonalInfo"], + typeof(AccountProfilePersonalInfoManagementGroupViewComponent) + ) + ); + } + + protected virtual async Task IsPasswordChangeEnabled(ProfileManagementPageCreationContext context) + { + var userManager = context.ServiceProvider.GetRequiredService(); + var currentUser = context.ServiceProvider.GetRequiredService(); + + var user = await userManager.GetByIdAsync(currentUser.GetId()); + + return !user.IsExternal; + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs new file mode 100644 index 0000000000..d65cc1fb0d --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/IProfileManagementPageContributor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + + namespace Volo.Abp.Account.Web.ProfileManagement +{ + public interface IProfileManagementPageContributor + { + Task ConfigureAsync(ProfileManagementPageCreationContext context); + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs new file mode 100644 index 0000000000..f3a9a80de7 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageCreationContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Account.Web.ProfileManagement +{ + public class ProfileManagementPageCreationContext + { + public IServiceProvider ServiceProvider { get; } + + public List Groups { get; } + + public ProfileManagementPageCreationContext(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + + Groups = new List(); + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs new file mode 100644 index 0000000000..ff491107aa --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageGroup.cs @@ -0,0 +1,39 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Account.Web.ProfileManagement +{ + public class ProfileManagementPageGroup + { + public string Id + { + get => _id; + set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id)); + } + private string _id; + + public string DisplayName + { + get => _displayName; + set => _displayName = Check.NotNullOrWhiteSpace(value, nameof(DisplayName)); + } + private string _displayName; + + public Type ComponentType + { + get => _componentType; + set => _componentType = Check.NotNull(value, nameof(ComponentType)); + } + private Type _componentType; + + public object Parameter { get; set; } + + public ProfileManagementPageGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null) + { + Id = id; + DisplayName = displayName; + ComponentType = componentType; + Parameter = parameter; + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs new file mode 100644 index 0000000000..91cf0ab0c1 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/ProfileManagement/ProfileManagementPageOptions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + + namespace Volo.Abp.Account.Web.ProfileManagement +{ + public class ProfileManagementPageOptions + { + public List Contributors { get; } + + public ProfileManagementPageOptions() + { + Contributors = new List(); + } + } +} From df780227f5f0486ae0c56d5ae26c326922fd8631 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Sun, 6 Sep 2020 21:05:22 +0300 Subject: [PATCH 22/69] fix: use an intersection type for strict null checks --- npm/ng-packs/packages/core/src/lib/models/config.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/models/config.ts b/npm/ng-packs/packages/core/src/lib/models/config.ts index be640a4dcf..cf4eb007c9 100644 --- a/npm/ng-packs/packages/core/src/lib/models/config.ts +++ b/npm/ng-packs/packages/core/src/lib/models/config.ts @@ -22,11 +22,12 @@ export namespace Config { logoUrl?: string; } - export interface ApiConfig { + export type ApiConfig = { [key: string]: string; - rootNamespace?: string; url: string; - } + } & Partial<{ + rootNamespace: string; + }>; export interface Apis { [key: string]: ApiConfig; From abb35daf2466296f57d312f441de7a18a53db087 Mon Sep 17 00:00:00 2001 From: EngincanV Date: Mon, 7 Sep 2020 09:01:30 +0300 Subject: [PATCH 23/69] CmsKit: Range attribute changes as DynamicRange --- .../Volo/CmsKit/Ratings/RatingConsts.cs | 4 ++-- .../Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs index d52bec599b..e3bf6c399d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Ratings/RatingConsts.cs @@ -8,8 +8,8 @@ namespace Volo.CmsKit.Ratings public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength; - public const int MaxStarCount = 5; + public static int MaxStarCount { get; set; } = 5; - public const int MinStarCount = 1; + public static int MinStarCount { get; set; } = 1; } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs index c6fa01b7d4..0577a97a68 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Ratings/CreateUpdateRatingInput.cs @@ -1,11 +1,13 @@ using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; using Volo.CmsKit.Ratings; namespace Volo.CmsKit.Public.Ratings { public class CreateUpdateRatingInput { - [Required, Range(RatingConsts.MinStarCount, RatingConsts.MaxStarCount)] + [Required] + [DynamicRange(typeof(RatingConsts), typeof(int), nameof(RatingConsts.MinStarCount), nameof(RatingConsts.MaxStarCount))] public short StarCount { get; set; } } } \ No newline at end of file From 80a1b0bfdb35faca35f8d7ef5161d7c6b211eca9 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 7 Sep 2020 17:22:02 +0800 Subject: [PATCH 24/69] Add unadded users/roles method to OrganizationUnitRepository --- .../Identity/IOrganizationUnitRepository.cs | 16 ++++++ .../Volo/Abp/Identity/OrganizationUnit.cs | 10 ++-- .../EfCoreOrganizationUnitRepository.cs | 52 +++++++++++++++++-- .../MongoOrganizationUnitRepository.cs | 37 +++++++++++++ .../OrganizationUnitRepository_Tests.cs | 21 ++++++++ 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs index 6650f62c82..81474425db 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs @@ -55,6 +55,14 @@ namespace Volo.Abp.Identity CancellationToken cancellationToken = default ); + Task> GetUnaddedRolesAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -71,6 +79,14 @@ namespace Volo.Abp.Identity CancellationToken cancellationToken = default ); + Task> GetUnaddedUsersAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + Task RemoveAllRolesAsync( OrganizationUnit organizationUnit, CancellationToken cancellationToken = default diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs index 3d1bbb0531..981a0f8fae 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs @@ -23,16 +23,16 @@ namespace Volo.Abp.Identity /// /// Hierarchical Code of this organization unit. /// Example: "00001.00042.00005". - /// This is a unique code for a Tenant. + /// This is a unique code for an OrganizationUnit. /// It's changeable if OU hierarchy is changed. /// public virtual string Code { get; internal set; } /// - /// Display name of this role. + /// Display name of this OrganizationUnit. /// - public virtual string DisplayName { get; set; } - + public virtual string DisplayName { get; set; } + /// /// Roles of this OU. /// @@ -77,7 +77,7 @@ namespace Volo.Abp.Identity } /// - /// Appends a child code to a parent code. + /// Appends a child code to a parent code. /// Example: if parentCode = "00001", childCode = "00042" then returns "00001.00042". /// /// Parent code. Can be null or empty if parent is a root. diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index 7bdb7d6837..d50745ab1d 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore { public class EfCoreOrganizationUnitRepository : EfCoreRepository, - IOrganizationUnitRepository + IOrganizationUnitRepository { public EfCoreOrganizationUnitRepository( IDbContextProvider dbContextProvider) @@ -56,6 +56,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } + public virtual async Task> GetListAsync( IEnumerable ids, bool includeDetails = false, @@ -111,6 +112,23 @@ namespace Volo.Abp.Identity.EntityFrameworkCore return await query.CountAsync(GetCancellationToken(cancellationToken)); } + public virtual async Task> GetUnaddedRolesAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToList(); + + return await DbContext.Roles + .Where(r => !roleIds.Contains(r.Id)) + .IncludeDetails(includeDetails) + .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) + .OrderBy(sorting ?? nameof(IdentityRole.Name)) + .ToListAsync(cancellationToken); + } + public virtual async Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -118,8 +136,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore int skipCount = 0, string filter = null, bool includeDetails = false, - CancellationToken cancellationToken = default - ) + CancellationToken cancellationToken = default) { var query = CreateGetMembersFilteredQuery(organizationUnit, filter); @@ -138,6 +155,35 @@ namespace Volo.Abp.Identity.EntityFrameworkCore return await query.CountAsync(GetCancellationToken(cancellationToken)); } + public virtual async Task> GetUnaddedUsersAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var userIdsInOrganizationUnit = DbContext.Set() + .Where(uou => uou.OrganizationUnitId == organizationUnit.Id) + .Select(uou => uou.UserId); + + var query = DbContext.Users + .Where(u => !userIdsInOrganizationUnit.Contains(u.Id)); + + if (!filter.IsNullOrWhiteSpace()) + { + query = query.Where(u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) || + (u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) + ); + } + + return await query + .IncludeDetails(includeDetails) + .OrderBy(sorting ?? nameof(IdentityUser.Name)) + .ToListAsync(cancellationToken); + } + public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 8c163d4126..2823c99a6c 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -107,6 +107,22 @@ namespace Volo.Abp.Identity.MongoDB .CountAsync(cancellationToken); } + public async Task> GetUnaddedRolesAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); + return await DbContext.Roles.AsQueryable() + .Where(r => !roleIds.Contains(r.Id)) + .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) + .OrderBy(sorting ?? nameof(IdentityRole.Name)) + .As>() + .ToListAsync(cancellationToken); + } + public virtual async Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -135,6 +151,27 @@ namespace Volo.Abp.Identity.MongoDB return await query.CountAsync(GetCancellationToken(cancellationToken)); } + public async Task> GetUnaddedUsersAsync( + OrganizationUnit organizationUnit, + string filter = null, + string sorting = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await DbContext.Users.AsQueryable() + .Where(u => !u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) || + (u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) + ) + .OrderBy(sorting ?? nameof(IdentityUser.UserName)) + .As>() + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public virtual Task RemoveAllRolesAsync(OrganizationUnit organizationUnit, CancellationToken cancellationToken = default) { organizationUnit.Roles.Clear(); diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs index bb8300d449..97544cd1e9 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs @@ -284,5 +284,26 @@ namespace Volo.Abp.Identity await uow.CompleteAsync(); } } + + [Fact] + public async Task GetUnaddedUsersOfOrganizationUnitAsync() + { + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + var unaddedUsers = await _organizationUnitRepository.GetUnaddedUsersAsync(ou); + + unaddedUsers.ShouldNotContain(u => u.UserName == "john.nash"); + unaddedUsers.ShouldContain(u => u.UserName == "administrator"); + } + + [Fact] + public async Task GetUnaddedRolesOfOrganizationUnitAsync() + { + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + var unaddedRoles = await _organizationUnitRepository.GetUnaddedRolesAsync(ou); + + unaddedRoles.ShouldNotContain(u => u.Name == "manager"); + unaddedRoles.ShouldNotContain(u => u.Name == "moderator"); + unaddedRoles.ShouldContain(u => u.Name.Contains("admin")); + } } } From 61b0bd6e7e5313fb888f0dd45fda60ee654842cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Mon, 7 Sep 2020 15:30:31 +0300 Subject: [PATCH 25/69] AbpComponentDemoSectionTagHelper updated --- .../AbpComponentDemoSectionTagHelper.cs | 82 ++++++++++++++----- .../wwwroot/libs/abp/luxon/abp.luxon.js | 46 +++++++++++ 2 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs index 5b3bd7791a..bace14b124 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.FileProviders; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers; +using Volo.Abp.Guids; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.TagHelpers @@ -17,10 +18,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S public string Title { get; set; } private readonly IVirtualFileProvider _virtualFileProvider; + private readonly IGuidGenerator _guidGenerator; - public AbpComponentDemoSectionTagHelper(IVirtualFileProvider virtualFileProvider) + public AbpComponentDemoSectionTagHelper( + IVirtualFileProvider virtualFileProvider, + IGuidGenerator guidGenerator) { _virtualFileProvider = virtualFileProvider; + _guidGenerator = guidGenerator; } public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) @@ -29,34 +34,69 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S var content = await output.GetChildContentAsync(); - output.PreContent.AppendHtml("
"); - output.PreContent.AppendHtml($"

{Title}

"); - output.PreContent.AppendHtml("
"); + var previewId = _guidGenerator.Create(); + var codeBlockId = _guidGenerator.Create(); + + var codeBlockTabId = _guidGenerator.Create(); + var tagHelperCodeBlockId = _guidGenerator.Create(); + var bootstrapCodeBlockId = _guidGenerator.Create(); + + output.PreContent.AppendHtml("
"); + output.PreContent.AppendHtml("
"); + output.PreContent.AppendHtml("
"); + output.PreContent.AppendHtml($"

{Title}

"); + output.PreContent.AppendHtml("
"); + output.PreContent.AppendHtml(""); + output.PreContent.AppendHtml("
"); // col-auto + output.PreContent.AppendHtml("
"); // row + output.PreContent.AppendHtml("
"); // card-header + output.PreContent.AppendHtml("
"); + output.PreContent.AppendHtml($"
"); + output.PreContent.AppendHtml($"
"); + /* component rendering here */ - output.PostContent.AppendHtml("
"); //abp-component-demo-section-body - AppendRawSource(output); - AppendBootstrapSource(output, content); - output.PostContent.AppendHtml("
"); //abp-component-demo-section - } - private static void AppendBootstrapSource(TagHelperOutput output, TagHelperContent content) - { - output.PostContent.AppendHtml("
"); - output.PostContent.AppendHtml("

Bootstrap

"); + output.PostContent.AppendHtml("
"); // tab-pane + output.PostContent.AppendHtml($"
"); + + /* CodeBlock tabs */ + + output.PostContent.AppendHtml($"
    "); + output.PostContent.AppendHtml("
  • "); + output.PostContent.AppendHtml($"Abp Tag Helper"); + output.PostContent.AppendHtml("
  • "); + output.PostContent.AppendHtml("
  • "); + output.PostContent.AppendHtml($"Bootstrap Render"); + output.PostContent.AppendHtml("
  • "); + output.PostContent.AppendHtml("
"); + output.PostContent.AppendHtml($"
"); + + output.PostContent.AppendHtml($"
"); output.PostContent.AppendHtml("
");
-            output.PostContent.Append(content.GetContent());
+            output.PostContent.AppendHtml("");
+            output.PostContent.Append(GetRawDemoSource());
+            output.PostContent.AppendHtml("");
             output.PostContent.AppendHtml("
"); output.PostContent.AppendHtml("
"); - } - - private void AppendRawSource(TagHelperOutput output) - { - output.PostContent.AppendHtml("
"); - output.PostContent.AppendHtml("

ABP Tag Helpers

"); + + output.PostContent.AppendHtml($"
"); output.PostContent.AppendHtml("
");
-            output.PostContent.Append(GetRawDemoSource());
+            output.PostContent.AppendHtml("");
+            output.PostContent.Append(content.GetContent());
+            output.PostContent.AppendHtml("");
             output.PostContent.AppendHtml("
"); output.PostContent.AppendHtml("
"); + + output.PostContent.AppendHtml("
"); // tab-content + output.PostContent.AppendHtml("
"); // tab-pane + output.PostContent.AppendHtml("
"); // tab-content + output.PostContent.AppendHtml("
"); // card-body + output.PostContent.AppendHtml("
"); // card } private string GetRawDemoSource() diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js new file mode 100644 index 0000000000..b04de7cadf --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js @@ -0,0 +1,46 @@ +var abp = abp || {}; +(function () { + + if (!luxon) { + throw "abp/luxon library requires the luxon library included to the page!"; + } + + /* TIMING *************************************************/ + + abp.timing = abp.timing || {}; + + var setObjectValue = function (obj, property, value) { + if (typeof property === "string") { + property = property.split('.'); + } + + if (property.length > 1) { + var p = property.shift(); + setObjectValue(obj[p], property, value); + } else { + obj[property[0]] = value; + } + } + + var getObjectValue = function (obj, property) { + return property.split('.').reduce((a, v) => a[v], obj) + } + + abp.timing.convertFieldsToIsoDate = function (form, fields) { + for (var field of fields) { + var dateTime = luxon.DateTime + .fromFormat( + getObjectValue(form, field), + abp.localization.currentCulture.dateTimeFormat.shortDatePattern, + {locale: abp.localization.currentCulture.cultureName} + ); + + if (!dateTime.invalid) { + setObjectValue(form, field, dateTime.toFormat("yyyy-MM-dd HH:mm:ss")) + } + } + + return form; + } + +})(jQuery); From fdc9b682d067679531b0c5af1060844700edcc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Mon, 7 Sep 2020 15:36:50 +0300 Subject: [PATCH 26/69] Update AbpComponentDemoSectionTagHelper.cs --- .../Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs index bace14b124..722460992d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs @@ -71,7 +71,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S output.PostContent.AppendHtml($"Abp Tag Helper"); output.PostContent.AppendHtml(""); output.PostContent.AppendHtml("
  • "); - output.PostContent.AppendHtml($"Bootstrap Render"); + output.PostContent.AppendHtml($"Bootstrap"); output.PostContent.AppendHtml("
  • "); output.PostContent.AppendHtml(""); output.PostContent.AppendHtml($"
    "); From 165b7c20ef65b338751345cda531d8e503eb12f0 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 7 Sep 2020 15:47:45 +0300 Subject: [PATCH 27/69] abp.io localization update --- .../AbpIoLocalization/Base/Localization/Resources/en.json | 1 + .../AbpIoLocalization/Community/Localization/Resources/en.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index a6ee47e253..d6f334f7cd 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -26,6 +26,7 @@ "ContributionGuide": "Contribution Guide", "Blog": "Blog", "Commercial": "Commercial", + "MyAccount": "My account", "SeeDocuments": "See Documents" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json index c74a26689f..286346a5b5 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json @@ -78,7 +78,6 @@ "Biography": "Biography", "HasNoPublishedArticlesYet": "has no published articles yet", "Author": "Author", - "MyAccount": "My account", "LatestGithubAnnouncements": "Latest Github Announcements", "SeeAllAnnouncements": "See All Announcements", "LatestBlogPost": "Latest Blog Post", From c6c56e316ff5f993bec15ea1e72b9a3f59bbeaf7 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 7 Sep 2020 16:48:07 +0300 Subject: [PATCH 28/69] identity module localizations & exceptions refactor --- .../Volo/Abp/Identity/Localization/ar.json | 56 ++++++------ .../Volo/Abp/Identity/Localization/cs.json | 58 ++++++------ .../Volo/Abp/Identity/Localization/de.json | 56 ++++++------ .../Volo/Abp/Identity/Localization/en.json | 14 +-- .../Volo/Abp/Identity/Localization/nl.json | 56 ++++++------ .../Volo/Abp/Identity/Localization/pl-PL.json | 58 ++++++------ .../Volo/Abp/Identity/Localization/pt-BR.json | 60 ++++++------- .../Volo/Abp/Identity/Localization/ru.json | 56 ++++++------ .../Volo/Abp/Identity/Localization/sl.json | 56 ++++++------ .../Volo/Abp/Identity/Localization/tr.json | 11 +-- .../Volo/Abp/Identity/Localization/vi.json | 56 ++++++------ .../Abp/Identity/Localization/zh-Hans.json | 61 +++++++------ .../Abp/Identity/Localization/zh-Hant.json | 89 +++++++------------ .../Volo/Abp/Identity/IdentityRoleManager.cs | 12 +-- .../Abp/Identity/OrganizationUnitManager.cs | 3 +- 15 files changed, 337 insertions(+), 365 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json index 41ac73c1b3..906f2a98f6 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "تأكيد كلمة المرور الجديدة", "PasswordChangedMessage": "تم تغيير كلمة مرورك بنجاح.", "PersonalSettingsSavedMessage": "تم حفظ إعداداتك الشخصية بنجاح.", - "Identity.DefaultError": "حدث خطأ.", - "Identity.ConcurrencyFailure": "فشل التزامن المتفائل ، تم تعديل الكائن.", - "Identity.DuplicateEmail": "البريد الإلكتروني '{0}' مستخدم بالفعل.", - "Identity.DuplicateRoleName": "اسم الدور '{0}' مأخوذ بالفعل.", - "Identity.DuplicateUserName": "اسم المستخدم '{0}' مأخوذ بالفعل.", - "Identity.InvalidEmail": "البريد الإلكتروني '{0}' غير صالح.", - "Identity.InvalidPasswordHasherCompatibilityMode": "وضع PasswordHasherCompatibilityMode غير صالح.", - "Identity.InvalidPasswordHasherIterationCount": "يجب أن يكون عدد التكرار عددًا صحيحًا موجبًا.", - "Identity.InvalidRoleName": "اسم الدور '{0}' غير صالح.", - "Identity.InvalidToken": "غير صالح token.", - "Identity.InvalidUserName": "اسم المستخدم '{0}' غير صالح ، يمكن أن يحتوي على أحرف أو أرقام فقط.", - "Identity.LoginAlreadyAssociated": "يوجد مستخدم لديه معلومات تسجيل الدخول هذه بالفعل.", - "Identity.PasswordMismatch": "كلمة مرور غير صحيحة.", - "Identity.PasswordRequiresDigit": "يجب أن تحتوي كلمات المرور على رقم واحد على الأقل ('0' - '9').", - "Identity.PasswordRequiresLower": "يجب أن تحتوي كلمات المرور على حرف صغير واحد على الأقل ('a' - 'z').", - "Identity.PasswordRequiresNonAlphanumeric": "يجب أن تحتوي كلمات المرور على حرف واحد غير أبجدي رقمي على الأقل.", - "Identity.PasswordRequiresUpper": "يجب أن تحتوي كلمات المرور على حرف كبير واحد على الأقل ('A' - 'Z').", - "Identity.PasswordTooShort": "يجب أن تتكون كلمات المرور من {0} حرف على الأقل.", - "Identity.RoleNotFound": "الدور {0} غير موجود.", - "Identity.UserAlreadyHasPassword": "لدى المستخدم بالفعل مجموعة كلمات مرور.", - "Identity.UserAlreadyInRole": "المستخدم بالفعل في الدور '{0}'.", - "Identity.UserLockedOut": "المستخدم مغلق.", - "Identity.UserLockoutNotEnabled": "التأمين غير ممكّن لهذا المستخدم.", - "Identity.UserNameNotFound": "المستخدم {0} غير موجود.", - "Identity.UserNotInRole": "المستخدم ليس في الدور '{0}'.", - "Identity.PasswordConfirmationFailed": "كلمة المرور لا تتطابق مع كلمة مرور التأكيد.", - "Identity.StaticRoleRenamingErrorMessage": "لا يمكن إعادة تسمية الأدوار الثابتة.", - "Identity.StaticRoleDeletionErrorMessage": "لا يمكن حذف الأدوار الثابتة.", + "Volo.Abp.Identity:DefaultError": "حدث خطأ.", + "Volo.Abp.Identity:ConcurrencyFailure": "فشل التزامن المتفائل ، تم تعديل الكائن.", + "Volo.Abp.Identity:DuplicateEmail": "البريد الإلكتروني '{0}' مستخدم بالفعل.", + "Volo.Abp.Identity:DuplicateRoleName": "اسم الدور '{0}' مأخوذ بالفعل.", + "Volo.Abp.Identity:DuplicateUserName": "اسم المستخدم '{0}' مأخوذ بالفعل.", + "Volo.Abp.Identity:InvalidEmail": "البريد الإلكتروني '{0}' غير صالح.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "وضع PasswordHasherCompatibilityMode غير صالح.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "يجب أن يكون عدد التكرار عددًا صحيحًا موجبًا.", + "Volo.Abp.Identity:InvalidRoleName": "اسم الدور '{0}' غير صالح.", + "Volo.Abp.Identity:InvalidToken": "غير صالح token.", + "Volo.Abp.Identity:InvalidUserName": "اسم المستخدم '{0}' غير صالح ، يمكن أن يحتوي على أحرف أو أرقام فقط.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "يوجد مستخدم لديه معلومات تسجيل الدخول هذه بالفعل.", + "Volo.Abp.Identity:PasswordMismatch": "كلمة مرور غير صحيحة.", + "Volo.Abp.Identity:PasswordRequiresDigit": "يجب أن تحتوي كلمات المرور على رقم واحد على الأقل ('0' - '9').", + "Volo.Abp.Identity:PasswordRequiresLower": "يجب أن تحتوي كلمات المرور على حرف صغير واحد على الأقل ('a' - 'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "يجب أن تحتوي كلمات المرور على حرف واحد غير أبجدي رقمي على الأقل.", + "Volo.Abp.Identity:PasswordRequiresUpper": "يجب أن تحتوي كلمات المرور على حرف كبير واحد على الأقل ('A' - 'Z').", + "Volo.Abp.Identity:PasswordTooShort": "يجب أن تتكون كلمات المرور من {0} حرف على الأقل.", + "Volo.Abp.Identity:RoleNotFound": "الدور {0} غير موجود.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "لدى المستخدم بالفعل مجموعة كلمات مرور.", + "Volo.Abp.Identity:UserAlreadyInRole": "المستخدم بالفعل في الدور '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "المستخدم مغلق.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "التأمين غير ممكّن لهذا المستخدم.", + "Volo.Abp.Identity:UserNameNotFound": "المستخدم {0} غير موجود.", + "Volo.Abp.Identity:UserNotInRole": "المستخدم ليس في الدور '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "كلمة المرور لا تتطابق مع كلمة مرور التأكيد.", + "Volo.Abp.Identity:010005": "لا يمكن إعادة تسمية الأدوار الثابتة.", + "Volo.Abp.Identity:010006": "لا يمكن حذف الأدوار الثابتة.", "Volo.Abp.Identity:010001": "لا يمكنك حذف حسابك الخاص!", "Permission:IdentityManagement": "إدارة الهوية", "Permission:RoleManagement": "إدارة الأدوار", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json index 04c85027a6..fde498c996 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json @@ -35,35 +35,35 @@ "DisplayName:NewPasswordConfirm": "Potvrzení nového hesla", "PasswordChangedMessage": "Vaše heslo bylo úspěšně změněno.", "PersonalSettingsSavedMessage": "Vaše osobní nastavení bylo úspěšně uloženo.", - "Identity.DefaultError": "Došlo k neznámé chybě.", - "Identity.ConcurrencyFailure": "Selhání optimistické souběžnosti, objekt byl změněn.", - "Identity.DuplicateEmail": "Email '{0}' již existuje.", - "Identity.DuplicateRoleName": "Role '{0}' již existuje.", - "Identity.DuplicateUserName": "Uživatelské jméno '{0}' již existuje.", - "Identity.InvalidEmail": "Email '{0}' je neplatný.", - "Identity.InvalidPasswordHasherCompatibilityMode": "Dodaný PasswordHasherCompatibilityMode je neplatný.", - "Identity.InvalidPasswordHasherIterationCount": "Počet iterací musí být kladné celé číslo.", - "Identity.InvalidRoleName": "Role '{0}' je neplatná.", - "Identity.InvalidToken": "Neplatný token.", - "Identity.InvalidUserName": "Uživatelské jméno '{0}' je neplatné, může obsahovat pouze písmena a číslice.", - "Identity.LoginAlreadyAssociated": "Uživatel s tímto přihlášením již existuje.", - "Identity.PasswordMismatch": "Chybné heslo.", - "Identity.PasswordRequiresDigit": "Hesla musí obsahovat alespoň jednu číslici ('0'-'9').", - "Identity.PasswordRequiresLower": "Hesla musí obsahovat alespoň jedno malé písmeno ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Hesla musí obsahovat alespoň jeden nealfanumerický znak.", - "Identity.PasswordRequiresUpper": "Hesla musí obsahovat alespoň jedno velké písmeno ('A'-'Z').", - "Identity.PasswordTooShort": "Hesla musí být dlouhá alespoň {0} znaků.", - "Identity.RoleNotFound": "Role {0} neexistuje.", - "Identity.UserAlreadyHasPassword": "Uživatel již má nastavené heslo.", - "Identity.UserAlreadyInRole": "Uživatel již je v roli '{0}'.", - "Identity.UserLockedOut": "Uživatel je uzamčen.", - "Identity.UserLockoutNotEnabled": "Uzamčení není umožněno pro tohoto uživatele.", - "Identity.UserNameNotFound": "Uživatel {0} neexistuje.", - "Identity.UserNotInRole": "Uživatel není v roli '{0}'.", - "Identity.PasswordConfirmationFailed": "Heslo nesouhlasí s potvrzovacím heslem.", - "Identity.StaticRoleRenamingErrorMessage": "Statické role nemohou být přejmenovány.", - "Identity.StaticRoleDeletionErrorMessage": "Statické role nemohou být smazány.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "Organizační jednotka s názvem {0} již existuje. Na stejné úrovni nelze vytvořit více jednotek se stejným názvem.", + "Volo.Abp.Identity:DefaultError": "Došlo k neznámé chybě.", + "Volo.Abp.Identity:ConcurrencyFailure": "Selhání optimistické souběžnosti, objekt byl změněn.", + "Volo.Abp.Identity:DuplicateEmail": "Email '{0}' již existuje.", + "Volo.Abp.Identity:DuplicateRoleName": "Role '{0}' již existuje.", + "Volo.Abp.Identity:DuplicateUserName": "Uživatelské jméno '{0}' již existuje.", + "Volo.Abp.Identity:InvalidEmail": "Email '{0}' je neplatný.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "Dodaný PasswordHasherCompatibilityMode je neplatný.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Počet iterací musí být kladné celé číslo.", + "Volo.Abp.Identity:InvalidRoleName": "Role '{0}' je neplatná.", + "Volo.Abp.Identity:InvalidToken": "Neplatný token.", + "Volo.Abp.Identity:InvalidUserName": "Uživatelské jméno '{0}' je neplatné, může obsahovat pouze písmena a číslice.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Uživatel s tímto přihlášením již existuje.", + "Volo.Abp.Identity:PasswordMismatch": "Chybné heslo.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Hesla musí obsahovat alespoň jednu číslici ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Hesla musí obsahovat alespoň jedno malé písmeno ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Hesla musí obsahovat alespoň jeden nealfanumerický znak.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Hesla musí obsahovat alespoň jedno velké písmeno ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Hesla musí být dlouhá alespoň {0} znaků.", + "Volo.Abp.Identity:RoleNotFound": "Role {0} neexistuje.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Uživatel již má nastavené heslo.", + "Volo.Abp.Identity:UserAlreadyInRole": "Uživatel již je v roli '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Uživatel je uzamčen.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Uzamčení není umožněno pro tohoto uživatele.", + "Volo.Abp.Identity:UserNameNotFound": "Uživatel {0} neexistuje.", + "Volo.Abp.Identity:UserNotInRole": "Uživatel není v roli '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Heslo nesouhlasí s potvrzovacím heslem.", + "Volo.Abp.Identity:010005": "Statické role nemohou být přejmenovány.", + "Volo.Abp.Identity:010006": "Statické role nemohou být smazány.", + "Volo.Abp.Identity:010004": "Organizační jednotka s názvem {0} již existuje. Na stejné úrovni nelze vytvořit více jednotek se stejným názvem.", "Identity.OrganizationUnit.MaxUserMembershipCount": "Maximální povolený počet členů organizační jednotky pro uživatele", "Volo.Abp.Identity:010001": "Nemůžete smazat svůj vlastní účet!", "Permission:IdentityManagement": "Správa identit", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json index ccd2b60cfc..a8da0baae1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen", "PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.", "PersonalSettingsSavedMessage": "Ihre persönlichen Einstellungen wurden erfolgreich gespeichert.", - "Identity.DefaultError": "Ein unbekannter Fehler ist aufgetreten.", - "Identity.ConcurrencyFailure": "Optimistischer Nebenläufigkeitsfehler, Objekt wurde geändert.", - "Identity.DuplicateEmail": "E-Mail '{0}' ist bereits vergeben.", - "Identity.DuplicateRoleName": "Der Rollenname '{0}' ist bereits vergeben.", - "Identity.DuplicateUserName": "Der Benutzername '{0}' ist bereits vergeben.", - "Identity.InvalidEmail": "E-Mail '{0}' ist ungültig.", - "Identity.InvalidPasswordHasherCompatibilityMode": "Der angegebene PasswordHasherCompatibilityMode ist ungültig.", - "Identity.InvalidPasswordHasherIterationCount": "Die Iterationszahl muss eine positive Ganzzahl sein.", - "Identity.InvalidRoleName": "Der Rollenname '{0}' ist ungültig.", - "Identity.InvalidToken": "Ungültiges Token.", - "Identity.InvalidUserName": "Benutzername '{0}' ist ungültig, kann nur Buchstaben oder Ziffern enthalten.", - "Identity.LoginAlreadyAssociated": "Ein Benutzer mit diesem Login existiert bereits.", - "Identity.PasswordMismatch": "Falsches Passwort.", - "Identity.PasswordRequiresDigit": "Passwörter müssen mindestens eine Ziffer ('0'-'9') enthalten.", - "Identity.PasswordRequiresLower": "Passwörter müssen mindestens einen Kleinbuchstaben ('a'-'z') enthalten.", - "Identity.PasswordRequiresNonAlphanumeric": "Passwörter müssen mindestens ein Sonderzeichen enthalten.", - "Identity.PasswordRequiresUpper": "Passwörter müssen mindestens einen Großbuchstaben ('A'-'Z') enthalten.", - "Identity.PasswordTooShort": "Passwörter müssen mindestens {0} Zeichen enthalten.", - "Identity.RoleNotFound": "Die Rolle {0} existiert nicht.", - "Identity.UserAlreadyHasPassword": "Der Benutzer hat bereits ein Passwort gesetzt.", - "Identity.UserAlreadyInRole": "Benutzer ist bereits in der Rolle '{0}'.", - "Identity.UserLockedOut": "Benutzer ist ausgesperrt.", - "Identity.UserLockoutNotEnabled": "Aussperrung ist für diesen Benutzer nicht aktiviert.", - "Identity.UserNameNotFound": "Benutzer {0} existiert nicht.", - "Identity.UserNotInRole": "Benutzer ist nicht in der Rolle '{0}'.", - "Identity.PasswordConfirmationFailed": "Passwort stimmt nicht mit dem Bestätigungspasswort überein.", - "Identity.StaticRoleRenamingErrorMessage": "Statische Rollen können nicht umbenannt werden.", - "Identity.StaticRoleDeletionErrorMessage": "Statische Rollen können nicht gelöscht werden.", + "Volo.Abp.Identity:DefaultError": "Ein unbekannter Fehler ist aufgetreten.", + "Volo.Abp.Identity:ConcurrencyFailure": "Optimistischer Nebenläufigkeitsfehler, Objekt wurde geändert.", + "Volo.Abp.Identity:DuplicateEmail": "E-Mail '{0}' ist bereits vergeben.", + "Volo.Abp.Identity:DuplicateRoleName": "Der Rollenname '{0}' ist bereits vergeben.", + "Volo.Abp.Identity:DuplicateUserName": "Der Benutzername '{0}' ist bereits vergeben.", + "Volo.Abp.Identity:InvalidEmail": "E-Mail '{0}' ist ungültig.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "Der angegebene PasswordHasherCompatibilityMode ist ungültig.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Die Iterationszahl muss eine positive Ganzzahl sein.", + "Volo.Abp.Identity:InvalidRoleName": "Der Rollenname '{0}' ist ungültig.", + "Volo.Abp.Identity:InvalidToken": "Ungültiges Token.", + "Volo.Abp.Identity:InvalidUserName": "Benutzername '{0}' ist ungültig, kann nur Buchstaben oder Ziffern enthalten.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Ein Benutzer mit diesem Login existiert bereits.", + "Volo.Abp.Identity:PasswordMismatch": "Falsches Passwort.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Passwörter müssen mindestens eine Ziffer ('0'-'9') enthalten.", + "Volo.Abp.Identity:PasswordRequiresLower": "Passwörter müssen mindestens einen Kleinbuchstaben ('a'-'z') enthalten.", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Passwörter müssen mindestens ein Sonderzeichen enthalten.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Passwörter müssen mindestens einen Großbuchstaben ('A'-'Z') enthalten.", + "Volo.Abp.Identity:PasswordTooShort": "Passwörter müssen mindestens {0} Zeichen enthalten.", + "Volo.Abp.Identity:RoleNotFound": "Die Rolle {0} existiert nicht.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Der Benutzer hat bereits ein Passwort gesetzt.", + "Volo.Abp.Identity:UserAlreadyInRole": "Benutzer ist bereits in der Rolle '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Benutzer ist ausgesperrt.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Aussperrung ist für diesen Benutzer nicht aktiviert.", + "Volo.Abp.Identity:UserNameNotFound": "Benutzer {0} existiert nicht.", + "Volo.Abp.Identity:UserNotInRole": "Benutzer ist nicht in der Rolle '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Passwort stimmt nicht mit dem Bestätigungspasswort überein.", + "Volo.Abp.Identity:010005": "Statische Rollen können nicht umbenannt werden.", + "Volo.Abp.Identity:010006": "Statische Rollen können nicht gelöscht werden.", "Volo.Abp.Identity:010001": "Sie können Ihr eigenes Konto nicht löschen!", "Permission:IdentityManagement": "Identitätsverwaltung", "Permission:RoleManagement": "Rollenverwaltung", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 8259e1abb2..9fe464ae96 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -61,11 +61,13 @@ "Volo.Abp.Identity:UserNameNotFound": "User {0} does not exist.", "Volo.Abp.Identity:UserNotInRole": "User is not in role '{0}'.", "Volo.Abp.Identity:PasswordConfirmationFailed": "Password does not match the confirm password.", - "Volo.Abp.Identity:StaticRoleRenamingErrorMessage": "Static roles can not be renamed.", - "Volo.Abp.Identity:StaticRoleDeletionErrorMessage": "Static roles can not be deleted.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "There is already an organization unit with name {0}. Two units with same name can not be created in same level.", - "Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organization unit membership count for a user", "Volo.Abp.Identity:010001": "You can not delete your own account!", + "Volo.Abp.Identity:010002": "Can not set more than {MaxUserMembershipCount} organization unit for a user!", + "Volo.Abp.Identity:010003": "Can not change password of an externally logged in user!", + "Volo.Abp.Identity:010004": "There is already an organization unit with name {0}. Two units with same name can not be created in same level.", + "Volo.Abp.Identity:010005": "Static roles can not be renamed.", + "Volo.Abp.Identity:010006": "Static roles can not be deleted.", + "Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organization unit membership count for a user", "Permission:IdentityManagement": "Identity management", "Permission:RoleManagement": "Role management", "Permission:Create": "Create", @@ -101,8 +103,6 @@ "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Whether the phoneNumber can be confirmed by the user.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.", - "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user.", - "Volo.Abp.Identity:010002": "Can not set more than {MaxUserMembershipCount} organization unit for a user!", - "Volo.Abp.Identity:010003": "Can not change password of an externally logged in user!" + "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user." } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json index 84ed47efce..1ca863e935 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Bevestig nieuw wachtwoord", "PasswordChangedMessage": "Uw wachtwoord is met succes veranderd.", "PersonalSettingsSavedMessage": "Uw persoonlijke instellingen zijn succesvol opgeslagen.", - "Identity.DefaultError": "Er is een onbekende fout opgetreden.", - "Identity.ConcurrencyFailure": "Optimistische gelijktijdigheidsfout, object is gewijzigd.", - "Identity.DuplicateEmail": "E-mail '{0}' is al in gebruik.", - "Identity.DuplicateRoleName": "De rolnaam '{0}' is al in gebruik.", - "Identity.DuplicateUserName": "Gebruikersnaam '{0}' is al in gebruik.", - "Identity.InvalidEmail": "E-mail' {0} 'is ongeldig.", - "Identity.InvalidPasswordHasherCompatibilityMode": "De opgegeven PasswordHasherCompatibilityMode is ongeldig.", - "Identity.InvalidPasswordHasherIterationCount": "De iteratietelling moet een positief geheel getal zijn.", - "Identity.InvalidRoleName": "Rolnaam '{0}' is ongeldig.", - "Identity.InvalidToken": "Ongeldig token.", - "Identity.InvalidUserName": "Gebruikersnaam '{0}' is ongeldig, mag alleen letters of cijfers bevatten.", - "Identity.LoginAlreadyAssociated": "Er bestaat al een gebruiker met deze login.", - "Identity.PasswordMismatch": "Incorrect wachtwoord.", - "Identity.PasswordRequiresDigit": "Wachtwoorden moeten minimaal één cijfer bevatten ('0' - '9').", - "Identity.PasswordRequiresLower": "Wachtwoorden moeten minstens één kleine letter bevatten ('a' - 'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Wachtwoorden moeten minimaal één niet-alfanumeriek teken bevatten.", - "Identity.PasswordRequiresUpper": "Wachtwoorden moeten ten minste één hoofdletter bevatten ('A' - 'Z').", - "Identity.PasswordTooShort": "Wachtwoorden moeten uit minimaal {0} tekens bestaan.", - "Identity.RoleNotFound": "Rol {0} bestaat niet.", - "Identity.UserAlreadyHasPassword": "Gebruiker heeft al een wachtwoord ingesteld.", - "Identity.UserAlreadyInRole": "Gebruiker al in rol '{0}'.", - "Identity.UserLockedOut": "Gebruiker is geblokkeerd.", - "Identity.UserLockoutNotEnabled": "Lockout is niet ingeschakeld voor deze gebruiker.", - "Identity.UserNameNotFound": "Gebruiker {0} bestaat niet.", - "Identity.UserNotInRole": "Gebruiker speelt geen rol '{0}'.", - "Identity.PasswordConfirmationFailed": "Wachtwoord komt niet overeen met de wachtwoord bevestiging.", - "Identity.StaticRoleRenamingErrorMessage": "Statische rollen kunnen niet worden hernoemd.", - "Identity.StaticRoleDeletionErrorMessage": "Statische rollen kunnen niet worden verwijderd.", + "Volo.Abp.Identity:DefaultError": "Er is een onbekende fout opgetreden.", + "Volo.Abp.Identity:ConcurrencyFailure": "Optimistische gelijktijdigheidsfout, object is gewijzigd.", + "Volo.Abp.Identity:DuplicateEmail": "E-mail '{0}' is al in gebruik.", + "Volo.Abp.Identity:DuplicateRoleName": "De rolnaam '{0}' is al in gebruik.", + "Volo.Abp.Identity:DuplicateUserName": "Gebruikersnaam '{0}' is al in gebruik.", + "Volo.Abp.Identity:InvalidEmail": "E-mail' {0} 'is ongeldig.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "De opgegeven PasswordHasherCompatibilityMode is ongeldig.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "De iteratietelling moet een positief geheel getal zijn.", + "Volo.Abp.Identity:InvalidRoleName": "Rolnaam '{0}' is ongeldig.", + "Volo.Abp.Identity:InvalidToken": "Ongeldig token.", + "Volo.Abp.Identity:InvalidUserName": "Gebruikersnaam '{0}' is ongeldig, mag alleen letters of cijfers bevatten.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Er bestaat al een gebruiker met deze login.", + "Volo.Abp.Identity:PasswordMismatch": "Incorrect wachtwoord.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Wachtwoorden moeten minimaal één cijfer bevatten ('0' - '9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Wachtwoorden moeten minstens één kleine letter bevatten ('a' - 'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Wachtwoorden moeten minimaal één niet-alfanumeriek teken bevatten.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Wachtwoorden moeten ten minste één hoofdletter bevatten ('A' - 'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Wachtwoorden moeten uit minimaal {0} tekens bestaan.", + "Volo.Abp.Identity:RoleNotFound": "Rol {0} bestaat niet.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Gebruiker heeft al een wachtwoord ingesteld.", + "Volo.Abp.Identity:UserAlreadyInRole": "Gebruiker al in rol '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Gebruiker is geblokkeerd.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Lockout is niet ingeschakeld voor deze gebruiker.", + "Volo.Abp.Identity:UserNameNotFound": "Gebruiker {0} bestaat niet.", + "Volo.Abp.Identity:UserNotInRole": "Gebruiker speelt geen rol '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Wachtwoord komt niet overeen met de wachtwoord bevestiging.", + "Volo.Abp.Identity:010005": "Statische rollen kunnen niet worden hernoemd.", + "Volo.Abp.Identity:010006": "Statische rollen kunnen niet worden verwijderd.", "Volo.Abp.Identity:010001": "U kunt uw eigen account niet verwijderen!", "Permission:IdentityManagement": "Identiteitsbeheer", "Permission:RoleManagement": "Rolbeheer", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index 54d137988d..e8094553a9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Powtierdź nowe hasło", "PasswordChangedMessage": "Twoje hasło zostało zmienione pomyślnie.", "PersonalSettingsSavedMessage": "Twoje personalne ustawienia zostały zapisane pomyślnie.", - "Identity.DefaultError": "Wystąpił niespodziewany błąd.", - "Identity.ConcurrencyFailure": "Optymistyczny błąd współbierzności, obiekt został zmodyfikowany.", - "Identity.DuplicateEmail": "Email '{0}' już istnieje.", - "Identity.DuplicateRoleName": "Rola '{0}' już istnieje.", - "Identity.DuplicateUserName": "Nazwa użytkownika '{0}' już istnieje.", - "Identity.InvalidEmail": "Email '{0}' jest niepoprawny.", - "Identity.InvalidPasswordHasherCompatibilityMode": "Dostawca PasswordHasherCompatibilityMode jest niepoprawny.", - "Identity.InvalidPasswordHasherIterationCount": "Wartość iteracji musi być większa od zera.", - "Identity.InvalidRoleName": "Rola '{0}' jest niepoprawna.", - "Identity.InvalidToken": "Niepoprawny token.", - "Identity.InvalidUserName": "Nazwa użytkownika '{0}' jest niepoprawna, może zawierać tylko litery i cyfry.", - "Identity.LoginAlreadyAssociated": "Użytkownik o tym loginie już istnieje.", - "Identity.PasswordMismatch": "Niepoprawne hasło.", - "Identity.PasswordRequiresDigit": "Hasło musi zawierać przynajmniej jedną cyfrę ('0'-'9').", - "Identity.PasswordRequiresLower": "Hasło musi zawierać przynajmniej jedną małą literę ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Hasło musi zawierać przynajmniej jeden znak alfanumeryczny.", - "Identity.PasswordRequiresUpper": "Hasło musi zawierać przynajmniej jedną wielką literę ('A'-'Z').", - "Identity.PasswordTooShort": "Hasło musi zawierać przynajmnie {0} znaków.", - "Identity.RoleNotFound": "Rola {0} nie istnieje.", - "Identity.UserAlreadyHasPassword": "Użytkownik ma już ustawione hasło.", - "Identity.UserAlreadyInRole": "Użytkownik jest już przypisany do roli '{0}'.", - "Identity.UserLockedOut": "Użytkownik jest zablokowany.", - "Identity.UserLockoutNotEnabled": "Blokada nie jest włączona dla tego użytkownika.", - "Identity.UserNameNotFound": "Użytkownik {0} nie istnieje.", - "Identity.UserNotInRole": "Użytkownik nie posiada roli '{0}'.", - "Identity.PasswordConfirmationFailed": "Podane hasła różnią się od siebie.", - "Identity.StaticRoleRenamingErrorMessage": "Statyczna rola nie może mieć zmienionej nazwy.", - "Identity.StaticRoleDeletionErrorMessage": "Statyczna rola nie może zostać usunięta.", + "Volo.Abp.Identity:DefaultError": "Wystąpił niespodziewany błąd.", + "Volo.Abp.Identity:ConcurrencyFailure": "Optymistyczny błąd współbierzności, obiekt został zmodyfikowany.", + "Volo.Abp.Identity:DuplicateEmail": "Email '{0}' już istnieje.", + "Volo.Abp.Identity:DuplicateRoleName": "Rola '{0}' już istnieje.", + "Volo.Abp.Identity:DuplicateUserName": "Nazwa użytkownika '{0}' już istnieje.", + "Volo.Abp.Identity:InvalidEmail": "Email '{0}' jest niepoprawny.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "Dostawca PasswordHasherCompatibilityMode jest niepoprawny.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Wartość iteracji musi być większa od zera.", + "Volo.Abp.Identity:InvalidRoleName": "Rola '{0}' jest niepoprawna.", + "Volo.Abp.Identity:InvalidToken": "Niepoprawny token.", + "Volo.Abp.Identity:InvalidUserName": "Nazwa użytkownika '{0}' jest niepoprawna, może zawierać tylko litery i cyfry.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Użytkownik o tym loginie już istnieje.", + "Volo.Abp.Identity:PasswordMismatch": "Niepoprawne hasło.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Hasło musi zawierać przynajmniej jedną cyfrę ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Hasło musi zawierać przynajmniej jedną małą literę ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Hasło musi zawierać przynajmniej jeden znak alfanumeryczny.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Hasło musi zawierać przynajmniej jedną wielką literę ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Hasło musi zawierać przynajmnie {0} znaków.", + "Volo.Abp.Identity:RoleNotFound": "Rola {0} nie istnieje.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Użytkownik ma już ustawione hasło.", + "Volo.Abp.Identity:UserAlreadyInRole": "Użytkownik jest już przypisany do roli '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Użytkownik jest zablokowany.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Blokada nie jest włączona dla tego użytkownika.", + "Volo.Abp.Identity:UserNameNotFound": "Użytkownik {0} nie istnieje.", + "Volo.Abp.Identity:UserNotInRole": "Użytkownik nie posiada roli '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Podane hasła różnią się od siebie.", + "Volo.Abp.Identity:010005": "Statyczna rola nie może mieć zmienionej nazwy.", + "Volo.Abp.Identity:010006": "Statyczna rola nie może zostać usunięta.", "Volo.Abp.Identity:010001": "Nie możesz usunąć swojego własnego konta!", "Permission:IdentityManagement": "Zarządzanie tożsamością", "Permission:RoleManagement": "Zarządanie rolami", @@ -73,4 +73,4 @@ "Permission:UserManagement": "Zarządzanie użytkownikami", "Permission:UserLookup": "Wyszukiwanie użytkownika" } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json index 1dda38b2a5..99c2289e66 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json @@ -35,35 +35,35 @@ "DisplayName:NewPasswordConfirm": "Confirmar nova senha", "PasswordChangedMessage": "Sua senha foi alterada com sucesso.", "PersonalSettingsSavedMessage": "Suas configurações pessoais foram salvas com sucesso.", - "Identity.DefaultError": "Ocorreu uma falha desconhecida.", - "Identity.ConcurrencyFailure": "Falha de concorrência otimista, o objeto foi modificado.", - "Identity.DuplicateEmail": "O e-mail '{0}' já está em uso.", - "Identity.DuplicateRoleName": "O nome de perfil '{0}' já está em uso.", - "Identity.DuplicateUserName": "O usuário '{0}' já está em uso.", - "Identity.InvalidEmail": "E-mail '{0}' inválido.", - "Identity.InvalidPasswordHasherCompatibilityMode": "O PasswordHasherCompatibilityMode é inválido.", - "Identity.InvalidPasswordHasherIterationCount": "A contagem de iterações deve ser um inteiro positivo.", - "Identity.InvalidRoleName": "Perfil '{0}' inválido.", - "Identity.InvalidToken": "Token inválido.", - "Identity.InvalidUserName": "Usuário '{0}' é inválido, somente pode conter letras ou números.", - "Identity.LoginAlreadyAssociated": "Um usuário com este login já existe.", - "Identity.PasswordMismatch": "Senha incorreta.", - "Identity.PasswordRequiresDigit": "Senhas devem possuir pelo menos um dígito ('0'-'9').", - "Identity.PasswordRequiresLower": "Senhas devem possuir pelo menos uma letra minúscula ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Senhas devem possuir pelo menos um caractere especial.", - "Identity.PasswordRequiresUpper": "Senhas devem possuir pelo menos uma letra maiúscula ('A'-'Z').", - "Identity.PasswordTooShort": "Senhas devem possuir pelo menos {0} caracteres.", - "Identity.RoleNotFound": "Perfil {0} não existe.", - "Identity.UserAlreadyHasPassword": "O usuário já possui uma senha.", - "Identity.UserAlreadyInRole": "Usuário já possui o perfil '{0}'.", - "Identity.UserLockedOut": "O usuário está bloqueado.", - "Identity.UserLockoutNotEnabled": "Este usuário não pode ser bloqueado.", - "Identity.UserNameNotFound": "Usuário {0} não existe.", - "Identity.UserNotInRole": "Usuário não está no perfil '{0}'.", - "Identity.PasswordConfirmationFailed": "A senha não confere com a confirmação de senha.", - "Identity.StaticRoleRenamingErrorMessage": "Perfis estáticos não podem ser renomeados.", - "Identity.StaticRoleDeletionErrorMessage": "Perfis estáticos não podem ser excluídos.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "Já existe uma unidade organizacional com o nome {0}. Duas unidades com o mesmo nome não pode ser criada no mesmo nível.", + "Volo.Abp.Identity:DefaultError": "Ocorreu uma falha desconhecida.", + "Volo.Abp.Identity:ConcurrencyFailure": "Falha de concorrência otimista, o objeto foi modificado.", + "Volo.Abp.Identity:DuplicateEmail": "O e-mail '{0}' já está em uso.", + "Volo.Abp.Identity:DuplicateRoleName": "O nome de perfil '{0}' já está em uso.", + "Volo.Abp.Identity:DuplicateUserName": "O usuário '{0}' já está em uso.", + "Volo.Abp.Identity:InvalidEmail": "E-mail '{0}' inválido.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "O PasswordHasherCompatibilityMode é inválido.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "A contagem de iterações deve ser um inteiro positivo.", + "Volo.Abp.Identity:InvalidRoleName": "Perfil '{0}' inválido.", + "Volo.Abp.Identity:InvalidToken": "Token inválido.", + "Volo.Abp.Identity:InvalidUserName": "Usuário '{0}' é inválido, somente pode conter letras ou números.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Um usuário com este login já existe.", + "Volo.Abp.Identity:PasswordMismatch": "Senha incorreta.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Senhas devem possuir pelo menos um dígito ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Senhas devem possuir pelo menos uma letra minúscula ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Senhas devem possuir pelo menos um caractere especial.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Senhas devem possuir pelo menos uma letra maiúscula ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Senhas devem possuir pelo menos {0} caracteres.", + "Volo.Abp.Identity:RoleNotFound": "Perfil {0} não existe.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "O usuário já possui uma senha.", + "Volo.Abp.Identity:UserAlreadyInRole": "Usuário já possui o perfil '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "O usuário está bloqueado.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Este usuário não pode ser bloqueado.", + "Volo.Abp.Identity:UserNameNotFound": "Usuário {0} não existe.", + "Volo.Abp.Identity:UserNotInRole": "Usuário não está no perfil '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "A senha não confere com a confirmação de senha.", + "Volo.Abp.Identity:010005": "Perfis estáticos não podem ser renomeados.", + "Volo.Abp.Identity:010006": "Perfis estáticos não podem ser excluídos.", + "Volo.Abp.Identity:010004": "Já existe uma unidade organizacional com o nome {0}. Duas unidades com o mesmo nome não pode ser criada no mesmo nível.", "Volo.Abp.Identity:010001": "Você não pode deletar sua própria conta!", "Permission:IdentityManagement": "Acessos", "Permission:RoleManagement": "Perfis", @@ -74,4 +74,4 @@ "Permission:UserManagement": "Usuários", "Permission:UserLookup": "Pesquisa de usuário" } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json index 1df7b63bfe..ebc304484a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Подтверждение нового пароля", "PasswordChangedMessage": "Ваш пароль был успешно изменен.", "PersonalSettingsSavedMessage": "Ваши персональные настройки были успешно сохранены.", - "Identity.DefaultError": "Возникла непредвиденная ошибка.", - "Identity.ConcurrencyFailure": "Ошибка оптимального управления многопоточностью, объект был изменен.", - "Identity.DuplicateEmail": "Электронная почта '{0}' уже зарегистрирована.", - "Identity.DuplicateRoleName": "Имя роли '{0}' уже занято.", - "Identity.DuplicateUserName": "Имя пользователя '{0}' уже занято.", - "Identity.InvalidEmail": "Адрес электронной почты '{0}' недействителен.", - "Identity.InvalidPasswordHasherCompatibilityMode": "Предоставленный PasswordHasherCompatibilityMode недействителен.", - "Identity.InvalidPasswordHasherIterationCount": "Число итераций должно быть положительным целым числом.", - "Identity.InvalidRoleName": "Имя роли '{0}' недопустимо.", - "Identity.InvalidToken": "Недопустимый маркер.", - "Identity.InvalidUserName": "Имя пользователя '{0}' недопустимо и может содержать только буквы или цифры.", - "Identity.LoginAlreadyAssociated": "Пользователь с таким логином уже существует.", - "Identity.PasswordMismatch": "Неверный пароль.", - "Identity.PasswordRequiresDigit": "Пароль должен содержать по крайней мере одну цифру.", - "Identity.PasswordRequiresLower": "Пароль должен содержать хотя бы одну строчную букву.", - "Identity.PasswordRequiresNonAlphanumeric": "Пароль должен иметь по крайней мере один не буквенно-цифровой символ.", - "Identity.PasswordRequiresUpper": "Пароль должен иметь хотя бы одну букву верхнего регистра.", - "Identity.PasswordTooShort": "Пароль должен содержать не менее {0} символов.", - "Identity.RoleNotFound": "Роль {0} не существует.", - "Identity.UserAlreadyHasPassword": "У пользователя уже установлен пароль.", - "Identity.UserAlreadyInRole": "Пользователь уже имеет роль '{0}'.", - "Identity.UserLockedOut": "Пользователь временно заблокирован.", - "Identity.UserLockoutNotEnabled": "Блокировка не активирована для этого пользователя.", - "Identity.UserNameNotFound": "Пользователь {0} не существует.", - "Identity.UserNotInRole": "Пользователь не имеет роль '{0}'.", - "Identity.PasswordConfirmationFailed": "Пароли не совпадают.", - "Identity.StaticRoleRenamingErrorMessage": "Статические роли не могут быть переименованы.", - "Identity.StaticRoleDeletionErrorMessage": "Статические роли не могут быть удалены.", + "Volo.Abp.Identity:DefaultError": "Возникла непредвиденная ошибка.", + "Volo.Abp.Identity:ConcurrencyFailure": "Ошибка оптимального управления многопоточностью, объект был изменен.", + "Volo.Abp.Identity:DuplicateEmail": "Электронная почта '{0}' уже зарегистрирована.", + "Volo.Abp.Identity:DuplicateRoleName": "Имя роли '{0}' уже занято.", + "Volo.Abp.Identity:DuplicateUserName": "Имя пользователя '{0}' уже занято.", + "Volo.Abp.Identity:InvalidEmail": "Адрес электронной почты '{0}' недействителен.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "Предоставленный PasswordHasherCompatibilityMode недействителен.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Число итераций должно быть положительным целым числом.", + "Volo.Abp.Identity:InvalidRoleName": "Имя роли '{0}' недопустимо.", + "Volo.Abp.Identity:InvalidToken": "Недопустимый маркер.", + "Volo.Abp.Identity:InvalidUserName": "Имя пользователя '{0}' недопустимо и может содержать только буквы или цифры.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Пользователь с таким логином уже существует.", + "Volo.Abp.Identity:PasswordMismatch": "Неверный пароль.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Пароль должен содержать по крайней мере одну цифру.", + "Volo.Abp.Identity:PasswordRequiresLower": "Пароль должен содержать хотя бы одну строчную букву.", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Пароль должен иметь по крайней мере один не буквенно-цифровой символ.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Пароль должен иметь хотя бы одну букву верхнего регистра.", + "Volo.Abp.Identity:PasswordTooShort": "Пароль должен содержать не менее {0} символов.", + "Volo.Abp.Identity:RoleNotFound": "Роль {0} не существует.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "У пользователя уже установлен пароль.", + "Volo.Abp.Identity:UserAlreadyInRole": "Пользователь уже имеет роль '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Пользователь временно заблокирован.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Блокировка не активирована для этого пользователя.", + "Volo.Abp.Identity:UserNameNotFound": "Пользователь {0} не существует.", + "Volo.Abp.Identity:UserNotInRole": "Пользователь не имеет роль '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Пароли не совпадают.", + "Volo.Abp.Identity:010005": "Статические роли не могут быть переименованы.", + "Volo.Abp.Identity:010006": "Статические роли не могут быть удалены.", "Volo.Abp.Identity:010001": "Вы не можете удалить свой собственный аккаунт!", "Permission:IdentityManagement": "Управление идентификацией", "Permission:RoleManagement": "Управление ролями", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json index 5f3cd96c55..8471735066 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Potrdite novo geslo", "PasswordChangedMessage": "Vaše geslo je bilo uspešno spremenjeno.", "PersonalSettingsSavedMessage": "Vaše osebne nastavitve so bile uspešno shranjene.", - "Identity.DefaultError": "Zgodila se je neznana napaka.", - "Identity.ConcurrencyFailure": "Napaka pri optimistični sočasnosti, objekt je bil spremenjen.", - "Identity.DuplicateEmail": "E-poštni naslov '{0}' je že zaseden.", - "Identity.DuplicateRoleName": "Naziv vloge '{0}' je že zasedeno.", - "Identity.DuplicateUserName": "Uporabniško ime '{0}' je že zasedeno.", - "Identity.InvalidEmail": "E-poštni naslov '{0}' ni veljaven.", - "Identity.InvalidPasswordHasherCompatibilityMode": "Navedeni PasswordHasherCompatibilityMode ni veljaven.", - "Identity.InvalidPasswordHasherIterationCount": "Število iteracij mora biti pozitivno celo število.", - "Identity.InvalidRoleName": "Naziv vloge '{0}' ni veljaven.", - "Identity.InvalidToken": "Neveljaven žeton.", - "Identity.InvalidUserName": "Uporabniško ime '{0}' ni veljavno, vsebuje lahko le črke in številke.", - "Identity.LoginAlreadyAssociated": "Uporabnik s to prijavo že obstaja.", - "Identity.PasswordMismatch": "Nepravilno geslo.", - "Identity.PasswordRequiresDigit": "Gesla morajo imeti vsaj eno številko ('0'-'9').", - "Identity.PasswordRequiresLower": "Gesla morajo imeti vsaj eno malo črko ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Gesla morajo imeti vsaj en ne-alfanumerični znak.", - "Identity.PasswordRequiresUpper": "Gesla morajo imeti vsaj eno veliko črko ('A'-'Z').", - "Identity.PasswordTooShort": "Gesla morajo biti dolga vsaj {0} znakov.", - "Identity.RoleNotFound": "Vloga {0} ne obstaja.", - "Identity.UserAlreadyHasPassword": "Uporabnik že ima nastavljeno geslo.", - "Identity.UserAlreadyInRole": "Uporabnik že ima dodeljeno vlogo '{0}'.", - "Identity.UserLockedOut": "Uporabnik je zaklenjen.", - "Identity.UserLockoutNotEnabled": "Zaklepanje za tega uporabnika ni omogočeno.", - "Identity.UserNameNotFound": "Uporabnik {0} ne obstaja.", - "Identity.UserNotInRole": "Uporabnik nima dodeljene vloge '{0}'.", - "Identity.PasswordConfirmationFailed": "Geslo se ne ujema s potrditvenim geslom.", - "Identity.StaticRoleRenamingErrorMessage": "Statičnih vlog ni mogoče preimenovati.", - "Identity.StaticRoleDeletionErrorMessage": "Statičnih vlog ni mogoče izbrisati.", + "Volo.Abp.Identity:DefaultError": "Zgodila se je neznana napaka.", + "Volo.Abp.Identity:ConcurrencyFailure": "Napaka pri optimistični sočasnosti, objekt je bil spremenjen.", + "Volo.Abp.Identity:DuplicateEmail": "E-poštni naslov '{0}' je že zaseden.", + "Volo.Abp.Identity:DuplicateRoleName": "Naziv vloge '{0}' je že zasedeno.", + "Volo.Abp.Identity:DuplicateUserName": "Uporabniško ime '{0}' je že zasedeno.", + "Volo.Abp.Identity:InvalidEmail": "E-poštni naslov '{0}' ni veljaven.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "Navedeni PasswordHasherCompatibilityMode ni veljaven.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Število iteracij mora biti pozitivno celo število.", + "Volo.Abp.Identity:InvalidRoleName": "Naziv vloge '{0}' ni veljaven.", + "Volo.Abp.Identity:InvalidToken": "Neveljaven žeton.", + "Volo.Abp.Identity:InvalidUserName": "Uporabniško ime '{0}' ni veljavno, vsebuje lahko le črke in številke.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Uporabnik s to prijavo že obstaja.", + "Volo.Abp.Identity:PasswordMismatch": "Nepravilno geslo.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Gesla morajo imeti vsaj eno številko ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Gesla morajo imeti vsaj eno malo črko ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Gesla morajo imeti vsaj en ne-alfanumerični znak.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Gesla morajo imeti vsaj eno veliko črko ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Gesla morajo biti dolga vsaj {0} znakov.", + "Volo.Abp.Identity:RoleNotFound": "Vloga {0} ne obstaja.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Uporabnik že ima nastavljeno geslo.", + "Volo.Abp.Identity:UserAlreadyInRole": "Uporabnik že ima dodeljeno vlogo '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Uporabnik je zaklenjen.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Zaklepanje za tega uporabnika ni omogočeno.", + "Volo.Abp.Identity:UserNameNotFound": "Uporabnik {0} ne obstaja.", + "Volo.Abp.Identity:UserNotInRole": "Uporabnik nima dodeljene vloge '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Geslo se ne ujema s potrditvenim geslom.", + "Volo.Abp.Identity:010005": "Statičnih vlog ni mogoče preimenovati.", + "Volo.Abp.Identity:010006": "Statičnih vlog ni mogoče izbrisati.", "Volo.Abp.Identity:010001": "Ne morete izbrisati svojega lastnega računa!", "Permission:IdentityManagement": "Upravljanje identitet", "Permission:RoleManagement": "Upravljanje vlog", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json index b270f344ef..2ad19c80f4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json @@ -35,8 +35,8 @@ "DisplayName:NewPasswordConfirm": "Yeni şifre (tekrar)", "PasswordChangedMessage": "Şifreniz başarıyla değiştirildi.", "PersonalSettingsSavedMessage": "Kişisel bilgileriniz başarıyla kaydedildi.", - "Volo.Abp.Identity:StaticRoleRenamingErrorMessage": "Bir Sabit rolün ismi değiştirilemez.", - "Volo.Abp.Identity:StaticRoleDeletionErrorMessage": "Bir Sabit rol silinemez.", + "Volo.Abp.Identity:010005": "Bir Sabit rolün ismi değiştirilemez.", + "Volo.Abp.Identity:010006": "Bir Sabit rol silinemez.", "Volo.Abp.Identity:DefaultError": "Bilinmeyen bir hata oluştu.", "Volo.Abp.Identity:ConcurrencyFailure": "İyimser eşzamanlılık hatası. Nesne değiştirilmiş.", "Volo.Abp.Identity:DuplicateEmail": "'{0}' email adresi zaten alınmış.", @@ -63,9 +63,11 @@ "Volo.Abp.Identity:UserNameNotFound": "{0} kullanıcısı bulunamadı.", "Volo.Abp.Identity:UserNotInRole": "Kullanıcı '{0}' rolünde değil.", "Volo.Abp.Identity:PasswordConfirmationFailed": "Yeni şifre ile onay şifresi uyuşmuyor.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "{0} isminde bir birim zaten var. Aynı seviyede aynı isimli iki birim olamaz.", - "Identity.OrganizationUnit.MaxUserMembershipCount": "Bir kullanıcı için izin verilen en fazla organizasyon birimi sayısı", "Volo.Abp.Identity:010001": "Kendi hesabınızı silemezsiniz!", + "Volo.Abp.Identity:010002": "Bir kullanıcı en fazla {MaxUserMembershipCount} organizasyon birimine üye olabilir!", + "Volo.Abp.Identity:010003": "Kimliği dışarıdan alınan kullanıcıların şifresi değiştirilemez!", + "Volo.Abp.Identity:010004": "{0} isminde bir birim zaten var. Aynı seviyede aynı isimli iki birim olamaz.", + "Identity.OrganizationUnit.MaxUserMembershipCount": "Bir kullanıcı için izin verilen en fazla organizasyon birimi sayısı", "Permission:IdentityManagement": "Kimlik yönetimi", "Permission:RoleManagement": "Rol yönetimi", "Permission:Create": "Oluşturma", @@ -74,7 +76,6 @@ "Permission:ChangePermissions": "İzinleri değiştirme", "Permission:UserManagement": "Kullanıcı yönetimi", "Permission:UserLookup": "Kullanıcı sorgulama", - "Volo.Abp.Identity:010002": "Bir kullanıcı en fazla {MaxUserMembershipCount} organizasyon birimine üye olabilir!", "DisplayName:Abp.Identity.Password.RequiredLength": "Uzunluk gerekli", "DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Tekil karakter gerekli", "DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Alfasayısal olmayan karakter gerekli", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json index 64690959b7..a241a4d515 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json @@ -35,34 +35,34 @@ "DisplayName:NewPasswordConfirm": "Xác nhận mật khẩu", "PasswordChangedMessage": "Mật khẩu của bạn đã được thay đổi thành công.", "PersonalSettingsSavedMessage": "Thiết lập của bạn đã được lưu lại thành công.", - "Identity.DefaultError": "Một thất bại chưa biết đã xảy ra.", - "Identity.ConcurrencyFailure": "Thất bại đồng thời lạc quan, đối tượng đã được sửa đổi.", - "Identity.DuplicateEmail": "Email '{0}' đã được sử dụng.", - "Identity.DuplicateRoleName": "Tên vài trò '{0}' đã được sử dụng.", - "Identity.DuplicateUserName": "User name '{0}' đã được sử dụng.", - "Identity.InvalidEmail": "Email '{0}' Không hợp lệ.", - "Identity.InvalidPasswordHasherCompatibilityMode": "PasswordHasherCompatibilityMode được cung cấp không hợp lệ.", - "Identity.InvalidPasswordHasherIterationCount": "Số lần lặp phải là số nguyên dương.", - "Identity.InvalidRoleName": "Tên người dùng '{0}' Không hợp lệ.", - "Identity.InvalidToken": "Token không hợp lệ.", - "Identity.InvalidUserName": "Tên người dùng '{0}' không hợp lệ, chỉ có thể chứa chữ cái hoặc chữ số.", - "Identity.LoginAlreadyAssociated": "Một người dùng có thông tin đăng nhập này đã tồn tại.", - "Identity.PasswordMismatch": "Mật khẩu không đúng.", - "Identity.PasswordRequiresDigit": "Mật khẩu phải có ít nhất một chữ số ('0'-'9').", - "Identity.PasswordRequiresLower": "Mật khẩu phải có ít nhất một chữ thường ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "Mật khẩu phải có ít nhất một ký tự không phải là chữ và số.", - "Identity.PasswordRequiresUpper": "Mật khẩu phải có ít nhất một chữ hoa ('A'-'Z').", - "Identity.PasswordTooShort": "Mật khẩu phải ít nhất {0} kí tự.", - "Identity.RoleNotFound": "Vai trò {0} không tồn tại.", - "Identity.UserAlreadyHasPassword": "Người dùng đã có một mật khẩu.", - "Identity.UserAlreadyInRole": "Người dùng đã có vai trò '{0}'.", - "Identity.UserLockedOut": "Người dùng bị khóa.", - "Identity.UserLockoutNotEnabled": "Khóa không được kích hoạt cho người dùng này.", - "Identity.UserNameNotFound": "Người dùng {0} không tồn tại.", - "Identity.UserNotInRole": "Người dùng không có vai trò '{0}'.", - "Identity.PasswordConfirmationFailed": "Mật khẩu không khớp với mật khẩu xác nhận.", - "Identity.StaticRoleRenamingErrorMessage": "Vai trò này là cố định không được phép đổi tên.", - "Identity.StaticRoleDeletionErrorMessage": "Vai trò này là cố định không được phép xóa.", + "Volo.Abp.Identity:DefaultError": "Một thất bại chưa biết đã xảy ra.", + "Volo.Abp.Identity:ConcurrencyFailure": "Thất bại đồng thời lạc quan, đối tượng đã được sửa đổi.", + "Volo.Abp.Identity:DuplicateEmail": "Email '{0}' đã được sử dụng.", + "Volo.Abp.Identity:DuplicateRoleName": "Tên vài trò '{0}' đã được sử dụng.", + "Volo.Abp.Identity:DuplicateUserName": "User name '{0}' đã được sử dụng.", + "Volo.Abp.Identity:InvalidEmail": "Email '{0}' Không hợp lệ.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "PasswordHasherCompatibilityMode được cung cấp không hợp lệ.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "Số lần lặp phải là số nguyên dương.", + "Volo.Abp.Identity:InvalidRoleName": "Tên người dùng '{0}' Không hợp lệ.", + "Volo.Abp.Identity:InvalidToken": "Token không hợp lệ.", + "Volo.Abp.Identity:InvalidUserName": "Tên người dùng '{0}' không hợp lệ, chỉ có thể chứa chữ cái hoặc chữ số.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "Một người dùng có thông tin đăng nhập này đã tồn tại.", + "Volo.Abp.Identity:PasswordMismatch": "Mật khẩu không đúng.", + "Volo.Abp.Identity:PasswordRequiresDigit": "Mật khẩu phải có ít nhất một chữ số ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "Mật khẩu phải có ít nhất một chữ thường ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "Mật khẩu phải có ít nhất một ký tự không phải là chữ và số.", + "Volo.Abp.Identity:PasswordRequiresUpper": "Mật khẩu phải có ít nhất một chữ hoa ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "Mật khẩu phải ít nhất {0} kí tự.", + "Volo.Abp.Identity:RoleNotFound": "Vai trò {0} không tồn tại.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "Người dùng đã có một mật khẩu.", + "Volo.Abp.Identity:UserAlreadyInRole": "Người dùng đã có vai trò '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "Người dùng bị khóa.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "Khóa không được kích hoạt cho người dùng này.", + "Volo.Abp.Identity:UserNameNotFound": "Người dùng {0} không tồn tại.", + "Volo.Abp.Identity:UserNotInRole": "Người dùng không có vai trò '{0}'.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "Mật khẩu không khớp với mật khẩu xác nhận.", + "Volo.Abp.Identity:010005": "Vai trò này là cố định không được phép đổi tên.", + "Volo.Abp.Identity:010006": "Vai trò này là cố định không được phép xóa.", "Volo.Abp.Identity:010001": "Bạn không thể xóa tài khoản của riêng bạn!", "Permission:IdentityManagement": "Quản lý danh tính", "Permission:RoleManagement": "Quản lý vai trò", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json index ffeb4d0d40..5494581c6a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json @@ -35,35 +35,35 @@ "DisplayName:NewPasswordConfirm": "确认新密码", "PasswordChangedMessage": "你已成功更改密码.", "PersonalSettingsSavedMessage": "你的个人设置保存成功.", - "Identity.DefaultError": "发生了一个未知错误.", - "Identity.ConcurrencyFailure": "对象已被修改,乐观并发失败.", - "Identity.DuplicateEmail": "邮箱 '{0}' 已存在.", - "Identity.DuplicateRoleName": "角色名 '{0}' 已存在.", - "Identity.DuplicateUserName": "用户名 '{0}' 已存在.", - "Identity.InvalidEmail": "邮箱 '{0}' 无效.", - "Identity.InvalidPasswordHasherCompatibilityMode": "提供的 PasswordHasherCompatibilityMode 无效.", - "Identity.InvalidPasswordHasherIterationCount": "迭代计数必须是正整数.", - "Identity.InvalidRoleName": "角色名 '{0}' 无效.", - "Identity.InvalidToken": "token无效.", - "Identity.InvalidUserName": "用户名 '{0}' 无效, 只能包含字母或数字.", - "Identity.LoginAlreadyAssociated": "此登录名的用户已存在.", - "Identity.PasswordMismatch": "密码错误.", - "Identity.PasswordRequiresDigit": "密码至少包含一位数字 ('0'-'9').", - "Identity.PasswordRequiresLower": "密码至少包含一位小写字母 ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "密码至少包含一位非字母数字字符.", - "Identity.PasswordRequiresUpper": "密码至少包含一位大写字母 ('A'-'Z').", - "Identity.PasswordTooShort": "密码至少为{0}个字符.", - "Identity.RoleNotFound": "角色 {0} 不存在.", - "Identity.UserAlreadyHasPassword": "用户已设置密码.", - "Identity.UserAlreadyInRole": "用户已具有角色 '{0}'.", - "Identity.UserLockedOut": "用户被锁定.", - "Identity.UserLockoutNotEnabled": "该用户未启用锁定.", - "Identity.UserNameNotFound": "用户 {0} 不存在.", - "Identity.UserNotInRole": "用户不具有 '{0}' 角色.", - "Identity.PasswordConfirmationFailed": "密码或确认密码不一致.", - "Identity.StaticRoleRenamingErrorMessage": "无法重命名静态角色.", - "Identity.StaticRoleDeletionErrorMessage": "无法删除静态角色.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.", + "Volo.Abp.Identity:DefaultError": "发生了一个未知错误.", + "Volo.Abp.Identity:ConcurrencyFailure": "对象已被修改,乐观并发失败.", + "Volo.Abp.Identity:DuplicateEmail": "邮箱 '{0}' 已存在.", + "Volo.Abp.Identity:DuplicateRoleName": "角色名 '{0}' 已存在.", + "Volo.Abp.Identity:DuplicateUserName": "用户名 '{0}' 已存在.", + "Volo.Abp.Identity:InvalidEmail": "邮箱 '{0}' 无效.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "提供的 PasswordHasherCompatibilityMode 无效.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "迭代计数必须是正整数.", + "Volo.Abp.Identity:InvalidRoleName": "角色名 '{0}' 无效.", + "Volo.Abp.Identity:InvalidToken": "token无效.", + "Volo.Abp.Identity:InvalidUserName": "用户名 '{0}' 无效, 只能包含字母或数字.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "此登录名的用户已存在.", + "Volo.Abp.Identity:PasswordMismatch": "密码错误.", + "Volo.Abp.Identity:PasswordRequiresDigit": "密码至少包含一位数字 ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "密码至少包含一位小写字母 ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "密码至少包含一位非字母数字字符.", + "Volo.Abp.Identity:PasswordRequiresUpper": "密码至少包含一位大写字母 ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "密码至少为{0}个字符.", + "Volo.Abp.Identity:RoleNotFound": "角色 {0} 不存在.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "用户已设置密码.", + "Volo.Abp.Identity:UserAlreadyInRole": "用户已具有角色 '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "用户被锁定.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "该用户未启用锁定.", + "Volo.Abp.Identity:UserNameNotFound": "用户 {0} 不存在.", + "Volo.Abp.Identity:UserNotInRole": "用户不具有 '{0}' 角色.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "密码或确认密码不一致.", + "Volo.Abp.Identity:010005": "无法重命名静态角色.", + "Volo.Abp.Identity:010006": "无法删除静态角色.", + "Volo.Abp.Identity:010004": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.", "Volo.Abp.Identity:010001": "您无法删除自己的帐户!", "Permission:IdentityManagement": "身份标识管理", "Permission:RoleManagement": "角色管理", @@ -99,6 +99,5 @@ "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "登录时是否需要验证的手机号码.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "是否允许用户更新用户名.", "Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允许用户更新电子邮箱." - } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json index b6e42acea2..edd21e10fe 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json @@ -35,35 +35,35 @@ "DisplayName:NewPasswordConfirm": "確認新密碼", "PasswordChangedMessage": "你已成功更改密碼.", "PersonalSettingsSavedMessage": "你的個人設置保存成功.", - "Identity.DefaultError": "發生了一個未知錯誤.", - "Identity.ConcurrencyFailure": "對象已被修改,因樂觀並行控制導致失敗.", - "Identity.DuplicateEmail": "電子信箱 '{0}' 已存在.", - "Identity.DuplicateRoleName": "角色名 '{0}' 已存在.", - "Identity.DuplicateUserName": "使用者名稱 '{0}' 已存在.", - "Identity.InvalidEmail": "電子信箱 '{0}' 無效.", - "Identity.InvalidPasswordHasherCompatibilityMode": "提供的 PasswordHasherCompatibilityMode 無效.", - "Identity.InvalidPasswordHasherIterationCount": "迭代計數必須是正整數.", - "Identity.InvalidRoleName": "角色名 '{0}' 無效.", - "Identity.InvalidToken": "token無效.", - "Identity.InvalidUserName": "使用者名稱 '{0}' 無效, 只能包含字母或數字.", - "Identity.LoginAlreadyAssociated": "此登入名的使用者已存在.", - "Identity.PasswordMismatch": "密碼錯誤.", - "Identity.PasswordRequiresDigit": "密碼至少包含一位數字 ('0'-'9').", - "Identity.PasswordRequiresLower": "密碼至少包含一位小寫字母 ('a'-'z').", - "Identity.PasswordRequiresNonAlphanumeric": "密碼至少包含一位非字母數字字元.", - "Identity.PasswordRequiresUpper": "密碼至少包含一位大寫字母 ('A'-'Z').", - "Identity.PasswordTooShort": "密碼至少為{0}個字元.", - "Identity.RoleNotFound": "角色 {0} 不存在.", - "Identity.UserAlreadyHasPassword": "使用者已設置密碼.", - "Identity.UserAlreadyInRole": "使用者已具有角色 '{0}'.", - "Identity.UserLockedOut": "使用者被鎖定.", - "Identity.UserLockoutNotEnabled": "該使用者未啟用鎖定.", - "Identity.UserNameNotFound": "使用者 {0} 不存在.", - "Identity.UserNotInRole": "使用者不具有 '{0}' 角色.", - "Identity.PasswordConfirmationFailed": "密碼或確認密碼不一致.", - "Identity.StaticRoleRenamingErrorMessage": "無法重命名靜態角色.", - "Identity.StaticRoleDeletionErrorMessage": "無法刪除靜態角色.", - "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "組織內單位內已包含名稱 {0}.同一層級組織,兩個單位不能有相同名稱", + "Volo.Abp.Identity:DefaultError": "發生了一個未知錯誤.", + "Volo.Abp.Identity:ConcurrencyFailure": "對象已被修改,因樂觀並行控制導致失敗.", + "Volo.Abp.Identity:DuplicateEmail": "電子信箱 '{0}' 已存在.", + "Volo.Abp.Identity:DuplicateRoleName": "角色名 '{0}' 已存在.", + "Volo.Abp.Identity:DuplicateUserName": "使用者名稱 '{0}' 已存在.", + "Volo.Abp.Identity:InvalidEmail": "電子信箱 '{0}' 無效.", + "Volo.Abp.Identity:InvalidPasswordHasherCompatibilityMode": "提供的 PasswordHasherCompatibilityMode 無效.", + "Volo.Abp.Identity:InvalidPasswordHasherIterationCount": "迭代計數必須是正整數.", + "Volo.Abp.Identity:InvalidRoleName": "角色名 '{0}' 無效.", + "Volo.Abp.Identity:InvalidToken": "token無效.", + "Volo.Abp.Identity:InvalidUserName": "使用者名稱 '{0}' 無效, 只能包含字母或數字.", + "Volo.Abp.Identity:LoginAlreadyAssociated": "此登入名的使用者已存在.", + "Volo.Abp.Identity:PasswordMismatch": "密碼錯誤.", + "Volo.Abp.Identity:PasswordRequiresDigit": "密碼至少包含一位數字 ('0'-'9').", + "Volo.Abp.Identity:PasswordRequiresLower": "密碼至少包含一位小寫字母 ('a'-'z').", + "Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "密碼至少包含一位非字母數字字元.", + "Volo.Abp.Identity:PasswordRequiresUpper": "密碼至少包含一位大寫字母 ('A'-'Z').", + "Volo.Abp.Identity:PasswordTooShort": "密碼至少為{0}個字元.", + "Volo.Abp.Identity:RoleNotFound": "角色 {0} 不存在.", + "Volo.Abp.Identity:UserAlreadyHasPassword": "使用者已設置密碼.", + "Volo.Abp.Identity:UserAlreadyInRole": "使用者已具有角色 '{0}'.", + "Volo.Abp.Identity:UserLockedOut": "使用者被鎖定.", + "Volo.Abp.Identity:UserLockoutNotEnabled": "該使用者未啟用鎖定.", + "Volo.Abp.Identity:UserNameNotFound": "使用者 {0} 不存在.", + "Volo.Abp.Identity:UserNotInRole": "使用者不具有 '{0}' 角色.", + "Volo.Abp.Identity:PasswordConfirmationFailed": "密碼或確認密碼不一致.", + "Volo.Abp.Identity:010005": "無法重命名靜態角色.", + "Volo.Abp.Identity:010006": "無法刪除靜態角色.", + "Volo.Abp.Identity:010004": "組織內單位內已包含名稱 {0}.同一層級組織,兩個單位不能有相同名稱", "Identity.OrganizationUnit.MaxUserMembershipCount": "允許一個使用者至多可隸屬在幾個組織單位", "Volo.Abp.Identity:010001": "您無法刪除自己的帳號!", "Permission:IdentityManagement": "身份識別管理", @@ -73,35 +73,6 @@ "Permission:Delete": "刪除", "Permission:ChangePermissions": "更改權限", "Permission:UserManagement": "使用者管理", - "Permission:UserLookup": "使用者查詢", - "DisplayName:Abp.Identity.Password.RequiredLength": "要求長度", - "DisplayName:Abp.Identity.Password.RequiredUniqueChars": "要求唯一字元數量", - "DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "要求非字母數字", - "DisplayName:Abp.Identity.Password.RequireLowercase": "要求小寫字母", - "DisplayName:Abp.Identity.Password.RequireUppercase": "要求大寫字母", - "DisplayName:Abp.Identity.Password.RequireDigit": "要求數字", - "DisplayName:Abp.Identity.Lockout.AllowedForNewUsers": "允許新使用者", - "DisplayName:Abp.Identity.Lockout.LockoutDuration": "鎖定時間(秒)", - "DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "最大失敗存取嘗試次數", - "DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "要求驗證的電子信箱", - "DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "啟用手機號碼驗證", - "DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "要求驗證的手機號碼", - "DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "啟用使用者名稱更新", - "DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "啟用電子信箱更新", - "Description:Abp.Identity.Password.RequiredLength": "密碼的最小長度.", - "Description:Abp.Identity.Password.RequiredUniqueChars": "密碼必須包含唯一字元的數量.", - "Description:Abp.Identity.Password.RequireNonAlphanumeric": "密碼是否必須包含非字母數字.", - "Description:Abp.Identity.Password.RequireLowercase": "密碼是否必須包含小寫字母.", - "Description:Abp.Identity.Password.RequireUppercase": "密碼是否必須包含大小字母.", - "Description:Abp.Identity.Password.RequireDigit": "密碼是否必須包含數字.", - "Description:Abp.Identity.Lockout.AllowedForNewUsers": "允許新使用者被鎖定.", - "Description:Abp.Identity.Lockout.LockoutDuration": "當鎖定發生時使用者被鎖定的時間(秒).", - "Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "如果啟用鎖定,當使用者被鎖定前失敗的存取嘗試次數.", - "Description:Abp.Identity.SignIn.RequireConfirmedEmail": "登入時是否需要驗證電子信箱.", - "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "使用者手機號碼是否需要驗證.", - "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "登入時是否需要驗證手機號碼.", - "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "是否允許使用者更新使用者名稱.", - "Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允許使用者更新電子信箱.", - "Volo.Abp.Identity:010002": "一個使用者不能設置超過 {MaxUserMembershipCount} 個組織單位下" + "Permission:UserLookup": "使用者查詢" } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs index 9c2a80a807..d2f2cb4a69 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs @@ -29,10 +29,10 @@ namespace Volo.Abp.Identity IStringLocalizer localizer, ICancellationTokenProvider cancellationTokenProvider) : base( - store, - roleValidators, - keyNormalizer, - errors, + store, + roleValidators, + keyNormalizer, + errors, logger) { Localizer = localizer; @@ -54,7 +54,7 @@ namespace Volo.Abp.Identity { if (role.IsStatic && role.Name != name) { - throw new BusinessException(Localizer["Identity.StaticRoleRenamingErrorMessage"]); // TODO: localize & change exception type + throw new BusinessException("Volo.Abp.Identity:010005"); } return await base.SetRoleNameAsync(role, name); @@ -64,7 +64,7 @@ namespace Volo.Abp.Identity { if (role.IsStatic) { - throw new BusinessException(Localizer["Identity.StaticRoleDeletionErrorMessage"]); // TODO: localize & change exception type + throw new BusinessException("Volo.Abp.Identity:010006"); } return await base.DeleteAsync(role); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index 4706f92428..d03e56af79 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs @@ -131,7 +131,8 @@ namespace Volo.Abp.Identity if (siblings.Any(ou => ou.DisplayName == organizationUnit.DisplayName)) { - throw new UserFriendlyException(Localizer["Identity.OrganizationUnit.DuplicateDisplayNameWarning", organizationUnit.DisplayName]); + throw new BusinessException("Volo.Abp.Identity:010004") + .WithData("0", organizationUnit.DisplayName); } } From 8c0e0b08ce8a8c005f1341efccb7904167131c62 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 7 Sep 2020 16:55:13 +0300 Subject: [PATCH 29/69] improve IdentityErrorCodes --- .../Volo/Abp/Identity/IdentityErrorCodes.cs | 3 +++ .../Volo/Abp/Identity/IdentityRoleManager.cs | 4 ++-- .../Volo/Abp/Identity/OrganizationUnitManager.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs index d5e57a6953..1defe67eeb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs @@ -5,5 +5,8 @@ public const string UserSelfDeletion = "Volo.Abp.Identity:010001"; public const string MaxAllowedOuMembership = "Volo.Abp.Identity:010002"; public const string ExternalUserPasswordChange = "Volo.Abp.Identity:010003"; + public const string DuplicateOrganizationUnitDisplayName = "Volo.Abp.Identity:010004"; + public const string StaticRoleRenaming = "Volo.Abp.Identity:010005"; + public const string StaticRoleDeletion = "Volo.Abp.Identity:010006"; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs index d2f2cb4a69..40c249e5c4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs @@ -54,7 +54,7 @@ namespace Volo.Abp.Identity { if (role.IsStatic && role.Name != name) { - throw new BusinessException("Volo.Abp.Identity:010005"); + throw new BusinessException(IdentityErrorCodes.StaticRoleRenaming); } return await base.SetRoleNameAsync(role, name); @@ -64,7 +64,7 @@ namespace Volo.Abp.Identity { if (role.IsStatic) { - throw new BusinessException("Volo.Abp.Identity:010006"); + throw new BusinessException(IdentityErrorCodes.StaticRoleDeletion); } return await base.DeleteAsync(role); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index d03e56af79..ec794a51df 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs @@ -131,7 +131,7 @@ namespace Volo.Abp.Identity if (siblings.Any(ou => ou.DisplayName == organizationUnit.DisplayName)) { - throw new BusinessException("Volo.Abp.Identity:010004") + throw new BusinessException(IdentityErrorCodes.DuplicateOrganizationUnitDisplayName) .WithData("0", organizationUnit.DisplayName); } } From dfd2baee7f8ad0fef68eff134014447084555c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 17:25:44 +0300 Subject: [PATCH 30/69] Stared to document the feature system --- docs/en/Features.md | 46 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 4b31ca16e9..d5262c6cdb 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -1,3 +1,47 @@ # Features -TODO \ No newline at end of file +ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. + +Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining features by any condition. + +> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). + +## Checking for the Features + +Before starting to explain how to define features, let's see how to check a feature in your application code. + +### RequiresFeature Attribute + +`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. + +**Example: Check if the current user/tenant has "PDF Reporting" feature enabled** + +```csharp +public class ReportingAppService : ApplicationService, IReportingAppService +{ + public async Task GetCsvReportAsync() + { + throw new System.NotImplementedException(); + } + + [RequiresFeature("MyApp.PdfReporting")] + public async Task GetPdfReportAsync() + { + throw new System.NotImplementedException(); + } +} +``` + +* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. +* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the +* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if current user/tenant has any of the features enabled. Use `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to allow only if all of the features are enabled. +* Multiple usage of `[RequiresFeature]` attribute is enabled, so it checks all of them. + +#### About the Interception + +ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class that is injected from the [dependency injection](Dependency-Injection.md). + +However, there are **some rules should be followed** in order to make it working; + +* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work). +* Only `async` methods (methods returning a `Task` or `Task`) are intercepted. From 735a0c2db68c054ad456afe4f6231317191bb5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 18:11:10 +0300 Subject: [PATCH 31/69] Update Features.md --- docs/en/Features.md | 115 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index d5262c6cdb..2c859d643f 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -2,6 +2,8 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. +The runtime value for a feature can simply be a `boolean`, like enabled (`true`) or disabled (`false`). However, you can store **any kind** of runtime value for feature. + Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining features by any condition. > The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). @@ -12,36 +14,125 @@ Before starting to explain how to define features, let's see how to check a feat ### RequiresFeature Attribute -`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. +`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. It is a useful shortcut for the `boolean` features. -**Example: Check if the current user/tenant has "PDF Reporting" feature enabled** +**Example: Check if the "PDF Reporting" feature enabled** ```csharp public class ReportingAppService : ApplicationService, IReportingAppService { - public async Task GetCsvReportAsync() - { - throw new System.NotImplementedException(); - } - [RequiresFeature("MyApp.PdfReporting")] public async Task GetPdfReportAsync() { - throw new System.NotImplementedException(); + //TODO... } } ``` * `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. -* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the -* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if current user/tenant has any of the features enabled. Use `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to allow only if all of the features are enabled. -* Multiple usage of `[RequiresFeature]` attribute is enabled, so it checks all of them. +* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature. +* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled. +* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP check checks all of them in that case. + +> Feature name can be any arbitrary string. It should be unique for a feature. #### About the Interception -ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class that is injected from the [dependency injection](Dependency-Injection.md). +ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md). However, there are **some rules should be followed** in order to make it working; * If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work). * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. + +> There is an exception for the controllers and razor pages. They don't require the following rules since ABP Framework uses action/page filters to implement the feature checking in this case. + +### IFeatureChecker Service + +`IFeatureChecker` allows to check a feature in your application code. + +#### IsEnabledAsync + +Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow. + +**Example: Check if the "PDF Reporting" feature enabled** + +```csharp +public class ReportingAppService : ApplicationService, IReportingAppService +{ + private readonly IFeatureChecker _featureChecker; + + public ReportingAppService(IFeatureChecker featureChecker) + { + _featureChecker = featureChecker; + } + + public async Task GetPdfReportAsync() + { + if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) + { + //TODO... + } + else + { + //TODO... + } + } +} +``` + +`IsEnabledAsync` has overloads to check multiple features in one method call. + +#### GetOrNullAsync + +Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`. + +**Example: Check the maximum product count allowed** + +```csharp +public class ProductController : AbpController +{ + private readonly IFeatureChecker _featureChecker; + + public ProductController(IFeatureChecker featureChecker) + { + _featureChecker = featureChecker; + } + + public async Task Create(CreateProductModel model) + { + var currentProductCount = await GetCurrentProductCountFromDatabase(); + + //GET THE FEATURE VALUE + var maxProductCountLimit = + await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); + + if (currentProductCount >= Convert.ToInt32(maxProductCountLimit)) + { + throw new BusinessException( + "MyApp:ReachToMaxProductCountLimit", + $"You can not create more than {maxProductCountLimit} products!" + ); + } + + //TODO: Create the product in the database... + } + + private async Task GetCurrentProductCountFromDatabase() + { + throw new System.NotImplementedException(); + } +} +``` + +In this way, you can create numeric limits in your SaaS application depending on the current user or tenant. + +Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: + +```csharp +var maxProductCountLimit = await _featureChecker.GetAsync("MyApp.MaxProductCount"); +``` + +#### Extension Methods + +TODO \ No newline at end of file From 460686a22d003e0475a62402d3f56b5e015e819f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 20:46:43 +0300 Subject: [PATCH 32/69] Added empty sections to the features document --- docs/en/Features.md | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 2c859d643f..82d1790b9e 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -2,19 +2,19 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. -The runtime value for a feature can simply be a `boolean`, like enabled (`true`) or disabled (`false`). However, you can store **any kind** of runtime value for feature. +The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. -Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining features by any condition. +Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining the features by any condition. > The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). ## Checking for the Features -Before starting to explain how to define features, let's see how to check a feature in your application code. +Before explaining to define features, let's see how to check a feature value in your application code. ### RequiresFeature Attribute -`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. It is a useful shortcut for the `boolean` features. +`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features. **Example: Check if the "PDF Reporting" feature enabled** @@ -42,10 +42,10 @@ ABP Framework uses the interception system to make the `[RequiresFeature]` attri However, there are **some rules should be followed** in order to make it working; -* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work). +* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. * Only `async` methods (methods returning a `Task` or `Task`) are intercepted. -> There is an exception for the controllers and razor pages. They don't require the following rules since ABP Framework uses action/page filters to implement the feature checking in this case. +> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case. ### IFeatureChecker Service @@ -125,7 +125,7 @@ public class ProductController : AbpController } ``` -In this way, you can create numeric limits in your SaaS application depending on the current user or tenant. +This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application. Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: @@ -135,4 +135,27 @@ var maxProductCountLimit = await _featureChecker.GetAsync("MyApp.MaxProduct #### Extension Methods +There are some useful extension methods for the `IFeatureChecker` interface; + +* `Task GetAsync(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`. +* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled). + +## Defining the Features + +TODO + +## Feature Management + +TODO + +## Advanced Topics + +TODO + +### Feature Value Providers + +TODO + +### Feature Store + TODO \ No newline at end of file From 60a54bc9f126cad6ce7f3a15200cf0e0c9f95c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:37:55 +0300 Subject: [PATCH 33/69] Added section: Defining the Features. --- docs/en/Features.md | 125 ++++++++++++++++++++++++++++- docs/en/images/features-action.png | Bin 0 -> 16742 bytes docs/en/images/features-modal.png | Bin 0 -> 35412 bytes 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 docs/en/images/features-action.png create mode 100644 docs/en/images/features-modal.png diff --git a/docs/en/Features.md b/docs/en/Features.md index 82d1790b9e..e125f16f08 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -4,7 +4,7 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. -Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining the features by any condition. +Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition. > The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). @@ -142,6 +142,129 @@ There are some useful extension methods for the `IFeatureChecker` interface; ## Defining the Features +A feature should be defined to be able to check it. + +### FeatureDefinitionProvider + +Create a class inheriting the `FeatureDefinitionProvider` to define permissions. + +**Example: Defining permissions** + +```csharp +using Volo.Abp.Features; + +namespace FeaturesDemo +{ + public class MyFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var myGroup = context.AddGroup("MyApp"); + + myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); + myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); + } + } +} +``` + +> ABP automatically discovers this class and registers the features. No additional configuration required. + +* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. +* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. +* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. + +### Other Feature Properties + +While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; + +* `DisplayName`: A localizable string that will be used to show the feature name on the user interface. +* `Description`: A longer localizable text to describe the feature. +* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types: + * `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI. + * `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI. + * `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI. +* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value. +* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization. + +So, based on these descriptions, it would be better to define these features as shown below: + +```csharp +using FeaturesDemo.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace FeaturesDemo +{ + public class MyFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var myGroup = context.AddGroup("MyApp"); + + myGroup.AddFeature( + "MyApp.PdfReporting", + defaultValue: "false", + displayName: LocalizableString + .Create("PdfReporting"), + valueType: new ToggleStringValueType() + ); + + myGroup.AddFeature( + "MyApp.MaxProductCount", + defaultValue: "10", + displayName: LocalizableString + .Create("MaxProductCount"), + valueType: new FreeTextStringValueType( + new NumericValueValidator(0, 1000000)) + ); + } + } +} + +``` + +* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. +* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`. + +Remember to define the localization the keys in your localization file: + +````json +"PdfReporting": "PDF Reporting", +"MaxProductCount": "Maximum number of products" +```` + +See the [localization document](Localization.md) for details about the localization system. + +### Feature Management Modal + +The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed. + +Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet): + +![features-action](images/features-action.png) + +This action opens a modal to manage the feature values for the selected tenant: + +![features-modal](images/features-modal.png) + +So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application. + +See the *Feature Management* section below to learn more about managing the features. + +### Child Features + +A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. + +TODO + +### Changing Features Definitions of a Depended Module + +TODO + +### Check a Permission in the Client Side + TODO ## Feature Management diff --git a/docs/en/images/features-action.png b/docs/en/images/features-action.png new file mode 100644 index 0000000000000000000000000000000000000000..8283a95be946232016b65a63f47c211424c6ba54 GIT binary patch literal 16742 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�UfiA=i< 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/features-modal.png b/docs/en/images/features-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..74ee8aabc33189a420f3205ddc0c90215e5767c9 GIT binary patch literal 35412 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 Q7XuJ@y85}Sb4q9e0P>=`F8}}l literal 0 HcmV?d00001 From 502ac9eea5d1dc769826a828fba72fd8e2ca6070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:49:04 +0300 Subject: [PATCH 34/69] added example: Defining child features --- docs/en/Features.md | 47 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index e125f16f08..963f15a9b7 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -222,7 +222,6 @@ namespace FeaturesDemo } } } - ``` * `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. @@ -257,7 +256,51 @@ See the *Feature Management* section below to learn more about managing the feat A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. -TODO +**Example: Defining child features** + +```csharp +using FeaturesDemo.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace FeaturesDemo +{ + public class MyFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var myGroup = context.AddGroup("MyApp"); + + var reportingFeature = myGroup.AddFeature( + "MyApp.Reporting", + defaultValue: "false", + displayName: LocalizableString + .Create("Reporting"), + valueType: new ToggleStringValueType() + ); + + reportingFeature.CreateChild( + "MyApp.PdfReporting", + defaultValue: "false", + displayName: LocalizableString + .Create("PdfReporting"), + valueType: new ToggleStringValueType() + ); + + reportingFeature.CreateChild( + "MyApp.ExcelReporting", + defaultValue: "false", + displayName: LocalizableString + .Create("ExcelReporting"), + valueType: new ToggleStringValueType() + ); + } + } +} +``` + +The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*. ### Changing Features Definitions of a Depended Module From 36f8bb65a98aa26cfe48b4735534b9629da1ea83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:58:30 +0300 Subject: [PATCH 35/69] Add link to the angular UI doc for the permissions and revise some sections. --- docs/en/Authorization.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 4b7502f437..eef4237039 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -80,6 +80,8 @@ namespace Acme.BookStore.Permissions > ABP automatically discovers this class. No additional configuration required! +> You typically define this class inside the `Application.Contracts` project of your [application](Startup-Templates/Application.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with. + In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group. When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role: @@ -276,14 +278,24 @@ public async Task CreateAsync(CreateAuthorDto input) > Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check. -### Check a Permission in JavaScript +## Check a Permission in JavaScript + +You may need to check a policy/permission on the client side. + +### MVC UI -You may need to check a policy/permission on the client side. For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. Example: +For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. + +**Example: Check if a given permission has been granted for the current user** ```js abp.auth.isGranted('MyPermissionName'); ``` +### Angular UI + +See the [permission management document](UI/Angular/Permission-Management.md) for the Angular UI. + ## Permission Management Permission management is normally done by an admin user using the permission management modal: @@ -374,7 +386,7 @@ Configure(options => ### Permission Store -`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. Permission management module implements it. See the [permission management module documentation](Modules/Permission-Management.md) for more information +`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](Modules/Permission-Management.md) for more information ### AlwaysAllowAuthorizationService From 8e45c9a240b9930eede375fc0470de51ee5b4673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:58:42 +0300 Subject: [PATCH 36/69] Add Feature Store section. --- docs/en/Features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 963f15a9b7..35ba5a0de0 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -306,7 +306,7 @@ The example above defines a *Reporting* feature with two children: *PDF Reportin TODO -### Check a Permission in the Client Side +## Check a Permission in the Client Side TODO @@ -324,4 +324,4 @@ TODO ### Feature Store -TODO \ No newline at end of file +`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information \ No newline at end of file From 0bae2564c127e359247e5ec87ba510eaf6f311c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 10:27:01 +0300 Subject: [PATCH 37/69] Create Angular UI features page --- docs/en/UI/Angular/Features.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/en/UI/Angular/Features.md diff --git a/docs/en/UI/Angular/Features.md b/docs/en/UI/Angular/Features.md new file mode 100644 index 0000000000..f749030a26 --- /dev/null +++ b/docs/en/UI/Angular/Features.md @@ -0,0 +1,3 @@ +# Angular UI: Features + +> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system. \ No newline at end of file From 00c5114b1e3ec8d52b4bdc0243bfdf34c6f6f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 10:29:04 +0300 Subject: [PATCH 38/69] Added sections to the features document --- docs/en/Features.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 35ba5a0de0..1ab26a113a 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -304,11 +304,38 @@ The example above defines a *Reporting* feature with two children: *PDF Reportin ### Changing Features Definitions of a Depended Module -TODO +A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. -## Check a Permission in the Client Side +**Example: Manipulate an existing feature definition** -TODO +```csharp +var someGroup = context.GetGroupOrNull("SomeModule"); +var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); +if (feature != null) +{ + feature.Description = ... + feature.CreateChild(...); +} +``` + +## Check a Feature in the Client Side + +A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI. + +### ASP.NET Core MVC / Razor Pages UI + +Use `abp.features` API to get the feature values. + +**Example: Get feature values in the JavaScript code** + +````js +var isEnabled = abp.features.values["MyApp.ExcelReporting"] === "true"; +var count = abp.features.values["MyApp.MaxProductCount"]; +```` + +### Angular UI + +See the [features](Features.md) document for the Angular UI. ## Feature Management From fcdcc3e6708feaaf8532a655106f65fe4940fafd Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:39:55 +0300 Subject: [PATCH 39/69] feat: create replaceable-components.service --- .../packages/core/src/lib/services/index.ts | 1 + .../replaceable-components.service.ts | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index c084fd4808..d3d13de20b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -9,6 +9,7 @@ export * from './localization.service'; export * from './multi-tenancy.service'; export * from './profile-state.service'; export * from './profile.service'; +export * from './replaceable-components.service'; export * from './rest.service'; export * from './routes.service'; export * from './session-state.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts new file mode 100644 index 0000000000..418e608c49 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -0,0 +1,65 @@ +import { Injectable, NgZone } from '@angular/core'; +import { Router } from '@angular/router'; +import { ReplaceableComponents } from '../models/replaceable-components'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { noop } from '../utils/common-utils'; +import { map, filter } from 'rxjs/operators'; + +@Injectable({ providedIn: 'root' }) +export class ReplaceableComponentsService { + private components$ = new BehaviorSubject([]); + + get replaceableComponents$(): Observable { + return this.components$.asObservable(); + } + + get replaceableComponents(): ReplaceableComponents.ReplaceableComponent[] { + return this.components$.value; + } + + constructor(private ngZone: NgZone, private router: Router) {} + + // TODO: Create a shared service for route reload and more + private reloadRoute() { + const { shouldReuseRoute } = this.router.routeReuseStrategy; + const setRouteReuse = (reuse: typeof shouldReuseRoute) => { + this.router.routeReuseStrategy.shouldReuseRoute = reuse; + }; + + setRouteReuse(() => false); + this.router.navigated = false; + + this.ngZone.run(async () => { + await this.router.navigateByUrl(this.router.url).catch(noop); + setRouteReuse(shouldReuseRoute); + }); + } + + add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { + let replaceableComponents = this.components$.value; + + const index = replaceableComponents.findIndex( + component => component.key === replaceableComponent.key, + ); + + if (index > -1) { + replaceableComponents[index] = replaceableComponent; + } else { + replaceableComponents = [...replaceableComponents, replaceableComponent]; + } + + this.components$.next(replaceableComponents); + + if (reload) this.reloadRoute(); + } + + get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent { + return this.replaceableComponents.find(component => component.key === replaceableComponentKey); + } + + get$(replaceableComponentKey: string): Observable { + return this.replaceableComponents$.pipe( + map(components => components.find(component => component.key === replaceableComponentKey)), + ); + } +} From a2f6235225001958db5151dbf4f2217c2c0d8d18 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:40:27 +0300 Subject: [PATCH 40/69] chore: deprecate replaceable-components.state --- .../actions/replaceable-components.actions.ts | 3 +- .../states/replaceable-components.state.ts | 30 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts index 8df6a384ad..a800ab858a 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts @@ -1,7 +1,8 @@ import { ReplaceableComponents } from '../models/replaceable-components'; +// tslint:disable: max-line-length /** - * @see usage: https://github.com/abpframework/abp/pull/2522#issue-358333183 + * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) */ export class AddReplaceableComponent { static readonly type = '[ReplaceableComponents] Add'; diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 82096c5472..e5caa9f95b 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,11 +1,14 @@ -import { Injectable, NgZone } from '@angular/core'; -import { Router } from '@angular/router'; +import { Injectable } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; import snq from 'snq'; import { AddReplaceableComponent } from '../actions/replaceable-components.actions'; import { ReplaceableComponents } from '../models/replaceable-components'; -import { noop } from '../utils/common-utils'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +// tslint:disable: max-line-length +/** + * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) + */ @State({ name: 'ReplaceableComponentsState', defaults: { replaceableComponents: [] } as ReplaceableComponents.State, @@ -30,23 +33,7 @@ export class ReplaceableComponentsState { return selector; } - constructor(private ngZone: NgZone, private router: Router) {} - - // TODO: Create a shared service for route reload and more - private reloadRoute() { - const { shouldReuseRoute } = this.router.routeReuseStrategy; - const setRouteReuse = (reuse: typeof shouldReuseRoute) => { - this.router.routeReuseStrategy.shouldReuseRoute = reuse; - }; - - setRouteReuse(() => false); - this.router.navigated = false; - - this.ngZone.run(async () => { - await this.router.navigateByUrl(this.router.url).catch(noop); - setRouteReuse(shouldReuseRoute); - }); - } + constructor(private service: ReplaceableComponentsService) {} @Action(AddReplaceableComponent) replaceableComponentsAction( @@ -69,6 +56,7 @@ export class ReplaceableComponentsState { replaceableComponents, }); - if (reload) this.reloadRoute(); + console.log(this.service); + this.service.add(payload, reload); } } From 7e7d86a745343c2e9c377a1c1e3752a45776c6ed Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:42:07 +0300 Subject: [PATCH 41/69] feat: implement the new service to project --- .../components/dynamic-layout.component.ts | 9 ++- .../replaceable-route-container.component.ts | 9 ++- .../replaceable-template.directive.ts | 9 ++- .../tests/dynamic-layout.component.spec.ts | 63 +++++++++---------- .../src/lib/providers/styles.provider.ts | 39 ++++++------ 5 files changed, 61 insertions(+), 68 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index c47b3c02fd..3d6870f577 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,13 +1,12 @@ import { Component, Injector, Optional, SkipSelf, Type } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { Store } from '@ngxs/store'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; import { ReplaceableComponents } from '../models/replaceable-components'; import { LocalizationService } from '../services/localization.service'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { RoutesService } from '../services/routes.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; import { findRoute, getRoutePath } from '../utils/route-utils'; import { TreeNode } from '../utils/tree-utils'; @@ -37,7 +36,7 @@ export class DynamicLayoutComponent { constructor( injector: Injector, private localizationService: LocalizationService, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, ) { @@ -67,7 +66,7 @@ export class DynamicLayoutComponent { if (!expectedLayout) expectedLayout = eLayoutType.empty; const key = this.layouts.get(expectedLayout); - this.layout = this.getComponent(key).component; + this.layout = this.getComponent(key)?.component; } }); @@ -82,6 +81,6 @@ export class DynamicLayoutComponent { } private getComponent(key: string): ReplaceableComponents.ReplaceableComponent { - return this.store.selectSnapshot(ReplaceableComponentsState.getComponent(key)); + return this.replaceableComponents.get(key); } } diff --git a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts index 04c596d398..6c0dabd511 100644 --- a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit, Type } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngxs/store'; import { distinctUntilChanged } from 'rxjs/operators'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; @Component({ selector: 'abp-replaceable-route-container', @@ -22,7 +21,7 @@ export class ReplaceableRouteContainerComponent implements OnInit { constructor( private route: ActivatedRoute, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, ) {} @@ -31,8 +30,8 @@ export class ReplaceableRouteContainerComponent implements OnInit { this.componentKey = (this.route.snapshot.data .replaceableComponent as ReplaceableComponents.RouteData).key; - const component$ = this.store - .select(ReplaceableComponentsState.getComponent(this.componentKey)) + const component$ = this.replaceableComponents + .get$(this.componentKey) .pipe(distinctUntilChanged()); this.subscription.addOne( diff --git a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts index fd9051c64e..489d1afabc 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts @@ -10,15 +10,14 @@ import { Type, ViewContainerRef, } from '@angular/core'; -import { Store } from '@ngxs/store'; import compare from 'just-compare'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import snq from 'snq'; import { ABP } from '../models/common'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; @Directive({ selector: '[abpReplaceableTemplate]', providers: [SubscriptionService] }) export class ReplaceableTemplateDirective implements OnInit, OnChanges { @@ -45,7 +44,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges { private templateRef: TemplateRef, private cfRes: ComponentFactoryResolver, private vcRef: ViewContainerRef, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, ) { this.context = { @@ -58,8 +57,8 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges { } ngOnInit() { - const component$ = this.store - .select(ReplaceableComponentsState.getComponent(this.data.componentKey)) + const component$ = this.replaceableComponents + .get$(this.data.componentKey) .pipe( filter( (res = {} as ReplaceableComponents.ReplaceableComponent) => diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 4d5f53a9ba..43ad6f87b1 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -7,7 +7,11 @@ import { NEVER } from 'rxjs'; import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; -import { ApplicationConfigurationService, RoutesService } from '../services'; +import { + ApplicationConfigurationService, + RoutesService, + ReplaceableComponentsService, +} from '../services'; import { ReplaceableComponentsState } from '../states'; @Component({ @@ -78,33 +82,7 @@ const routes: ABP.Route[] = [ }, ]; -const storeData = { - ReplaceableComponentsState: { - replaceableComponents: [ - { - key: 'Theme.ApplicationLayoutComponent', - component: DummyApplicationLayoutComponent, - }, - { - key: 'Theme.AccountLayoutComponent', - component: DummyAccountLayoutComponent, - }, - { - key: 'Theme.EmptyLayoutComponent', - component: DummyEmptyLayoutComponent, - }, - ], - }, -}; - describe('DynamicLayoutComponent', () => { - const mockActions: Actions = NEVER; - const mockStore = ({ - selectSnapshot() { - return true; - }, - } as unknown) as Store; - const createComponent = createRoutingFactory({ component: RouterOutletComponent, stubsEnabled: false, @@ -113,10 +91,16 @@ describe('DynamicLayoutComponent', () => { providers: [ { provide: RoutesService, - useFactory: () => new RoutesService(mockActions, mockStore), + useFactory: () => + new RoutesService(NEVER, ({ + selectSnapshot() { + return true; + }, + } as unknown) as Store), }, + ReplaceableComponentsService, ], - imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot([ReplaceableComponentsState])], + imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot()], routes: [ { path: '', component: RouterOutletComponent }, { @@ -163,15 +147,26 @@ describe('DynamicLayoutComponent', () => { }); let spectator: SpectatorRouting; - let store: Store; + let replaceableComponents: ReplaceableComponentsService; beforeEach(async () => { spectator = createComponent(); - store = spectator.inject(Store); + replaceableComponents = spectator.inject(ReplaceableComponentsService); const routesService = spectator.inject(RoutesService); routesService.add(routes); - store.reset(storeData); + replaceableComponents.add({ + key: 'Theme.ApplicationLayoutComponent', + component: DummyApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.AccountLayoutComponent', + component: DummyAccountLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.EmptyLayoutComponent', + component: DummyEmptyLayoutComponent, + }); }); it('should handle application layout from parent abp route and display it', async () => { @@ -204,8 +199,8 @@ describe('DynamicLayoutComponent', () => { }); it('should not display any layout when layouts are empty', async () => { - store.reset({ ...storeData, ReplaceableComponentsState: {} }); - + const spy = jest.spyOn(replaceableComponents, 'get'); + spy.mockReturnValue(null); spectator.detectChanges(); spectator.router.navigateByUrl('/withoutLayout'); diff --git a/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts b/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts index ab6ea9cd1e..b823503912 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts @@ -1,4 +1,4 @@ -import { AddReplaceableComponent, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core'; +import { ReplaceableComponentsService, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core'; import { APP_INITIALIZER } from '@angular/core'; import { Store } from '@ngxs/store'; import { AccountLayoutComponent } from '../components/account-layout/account-layout.component'; @@ -11,32 +11,33 @@ export const BASIC_THEME_STYLES_PROVIDERS = [ { provide: APP_INITIALIZER, useFactory: configureStyles, - deps: [DomInsertionService, Store], + deps: [DomInsertionService, ReplaceableComponentsService], multi: true, }, ]; -export function configureStyles(domInsertion: DomInsertionService, store: Store) { +export function configureStyles( + domInsertion: DomInsertionService, + replaceableComponents: ReplaceableComponentsService, +) { return () => { domInsertion.insertContent(CONTENT_STRATEGY.AppendStyleToHead(styles)); - initLayouts(store); + initLayouts(replaceableComponents); }; } -function initLayouts(store: Store) { - store.dispatch([ - new AddReplaceableComponent({ - key: eThemeBasicComponents.ApplicationLayout, - component: ApplicationLayoutComponent, - }), - new AddReplaceableComponent({ - key: eThemeBasicComponents.AccountLayout, - component: AccountLayoutComponent, - }), - new AddReplaceableComponent({ - key: eThemeBasicComponents.EmptyLayout, - component: EmptyLayoutComponent, - }), - ]); +function initLayouts(replaceableComponents: ReplaceableComponentsService) { + replaceableComponents.add({ + key: eThemeBasicComponents.ApplicationLayout, + component: ApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: eThemeBasicComponents.AccountLayout, + component: AccountLayoutComponent, + }); + replaceableComponents.add({ + key: eThemeBasicComponents.EmptyLayout, + component: EmptyLayoutComponent, + }); } From 731539ef84a655c47e92891417aa1a72dafc236a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 10:43:42 +0300 Subject: [PATCH 42/69] Added Feature Management section. --- docs/en/Features.md | 35 +++++++++++++++++++++++- docs/en/Modules/Feature-Management.md | 2 ++ docs/en/Modules/Permission-Management.md | 2 ++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 1ab26a113a..05712eb803 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -170,6 +170,8 @@ namespace FeaturesDemo > ABP automatically discovers this class and registers the features. No additional configuration required. +> This class is generally created in the `Application.Contracts` project of your solution. + * In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. * First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. * Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. @@ -339,7 +341,38 @@ See the [features](Features.md) document for the Angular UI. ## Feature Management -TODO +Feature management is normally done by an admin user using the feature management modal: + +![features-modal](images/features-modal.png) + +This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action. + +If you need to manage features by code, inject the `IFeatureManager` service. + +**Example: Enable PDF reporting for a tenant** + +```csharp +public class MyService : ITransientDependency +{ + private readonly IFeatureManager _featureManager; + + public MyService(IFeatureManager featureManager) + { + _featureManager = featureManager; + } + + public async Task EnablePdfReporting(Guid tenantId) + { + await _featureManager.SetForTenantAsync( + tenantId, + "MyApp.PdfReporting", + true.ToString() + ); + } +} +``` + +`IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information. ## Advanced Topics diff --git a/docs/en/Modules/Feature-Management.md b/docs/en/Modules/Feature-Management.md index 5c41277dd8..968b742092 100644 --- a/docs/en/Modules/Feature-Management.md +++ b/docs/en/Modules/Feature-Management.md @@ -1,3 +1,5 @@ # Feature Management Module +> This module implements the `IFeatureStore` to store and manage feature values in a database. See the [Features System document](../Features.md) to understand the features first. + TODO \ No newline at end of file diff --git a/docs/en/Modules/Permission-Management.md b/docs/en/Modules/Permission-Management.md index faa03eecc9..ad48b60171 100644 --- a/docs/en/Modules/Permission-Management.md +++ b/docs/en/Modules/Permission-Management.md @@ -1,3 +1,5 @@ # Permission Management Module +This module implements the `IPermissionStore` to store and manage feature values in a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems first. + TODO \ No newline at end of file From 5c173a692e6d927d82688cca0c446f054d0fcf63 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 8 Sep 2020 15:54:12 +0800 Subject: [PATCH 43/69] Group features on the feature management modal --- .../Abp/Features/FeatureDefinitionManager.cs | 9 +- .../Abp/Features/IFeatureDefinitionManager.cs | 4 +- .../Abp/FeatureManagement/FeatureGroupDto.cs | 18 ++++ .../Abp/FeatureManagement/FeatureListDto.cs | 9 -- .../GetFeatureListResultDto.cs | 9 ++ .../FeatureManagement/IFeatureAppService.cs | 2 +- .../FeatureManagement/FeatureAppService.cs | 55 +++++++---- .../FeatureManagement/FeaturesController.cs | 2 +- .../FeatureManagementModal.cshtml | 98 +++++++++++-------- .../FeatureManagementModal.cshtml.cs | 19 +++- .../FeatureAppService_Tests.cs | 4 +- .../StringValueJsonConverter_Tests.cs | 82 +++++++++------- 12 files changed, 190 insertions(+), 121 deletions(-) create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs delete mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs index f8e8d9e9fe..31498e0756 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs @@ -62,6 +62,11 @@ namespace Volo.Abp.Features return FeatureDefinitions.GetOrDefault(name); } + public IReadOnlyList GetGroups() + { + return FeatureGroupDefinitions.Values.ToImmutableList(); + } + protected virtual Dictionary CreateFeatureDefinitions() { var features = new Dictionary(); @@ -78,7 +83,7 @@ namespace Volo.Abp.Features } protected virtual void AddFeatureToDictionaryRecursively( - Dictionary features, + Dictionary features, FeatureDefinition feature) { if (features.ContainsKey(feature.Name)) @@ -114,4 +119,4 @@ namespace Volo.Abp.Features return context.Groups; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs index c0172d7bc3..ca0a8cbcc6 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs @@ -11,5 +11,7 @@ namespace Volo.Abp.Features IReadOnlyList GetAll(); FeatureDefinition GetOrNull(string name); + + IReadOnlyList GetGroups(); } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs new file mode 100644 index 0000000000..dea10fd298 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Volo.Abp.FeatureManagement +{ + public class FeatureGroupDto + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public List Features { get; set; } + + public string GetNormalizedGroupName() + { + return Name.Replace(".", "_"); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs deleted file mode 100644 index ee7bad8d5c..0000000000 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.FeatureManagement -{ - public class FeatureListDto - { - public List Features { get; set; } - } -} \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs new file mode 100644 index 0000000000..86329598cc --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.FeatureManagement +{ + public class GetFeatureListResultDto + { + public List Groups { get; set; } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs index 2c337f1b2a..00c36b7f55 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.FeatureManagement { public interface IFeatureAppService : IApplicationService { - Task GetAsync([NotNull] string providerName, [NotNull] string providerKey); + Task GetAsync([NotNull] string providerName, [NotNull] string providerKey); Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input); } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index f0c310650d..dbe649e51f 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; namespace Volo.Abp.FeatureManagement @@ -24,35 +25,49 @@ namespace Volo.Abp.FeatureManagement Options = options.Value; } - public virtual async Task GetAsync([NotNull] string providerName, [NotNull] string providerKey) + public virtual async Task GetAsync([NotNull] string providerName, [NotNull] string providerKey) { await CheckProviderPolicy(providerName); - var featureDefinitions = FeatureDefinitionManager.GetAll(); - var features = new List(); + var result = new GetFeatureListResultDto + { + Groups = new List() + }; - foreach (var featureDefinition in featureDefinitions) + foreach (var group in FeatureDefinitionManager.GetGroups()) { - var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey); - features.Add(new FeatureDto + var groupDto = new FeatureGroupDto + { + Name = group.Name, + DisplayName = group.DisplayName.Localize(StringLocalizerFactory), + Features = new List() + }; + + foreach (var featureDefinition in group.GetFeaturesWithChildren()) { - Name = featureDefinition.Name, - DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), - ValueType = featureDefinition.ValueType, - Description = featureDefinition.Description?.Localize(StringLocalizerFactory), - ParentName = featureDefinition.Parent?.Name, - Value = feature.Value, - Provider = new FeatureProviderDto + var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey); + groupDto.Features.Add(new FeatureDto { - Name = feature.Provider?.Name, - Key = feature.Provider?.Key - } - }); - } + Name = featureDefinition.Name, + DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), + ValueType = featureDefinition.ValueType, + Description = featureDefinition.Description?.Localize(StringLocalizerFactory), + ParentName = featureDefinition.Parent?.Name, + Value = feature.Value, + Provider = new FeatureProviderDto + { + Name = feature.Provider?.Name, + Key = feature.Provider?.Key + } + }); + } + + SetFeatureDepth(groupDto.Features, providerName, providerKey); - SetFeatureDepth(features, providerName, providerKey); + result.Groups.Add(groupDto); + } - return new FeatureListDto { Features = features }; + return result; } public virtual async Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs index 0ae78ed03f..cc00fb370b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.FeatureManagement } [HttpGet] - public virtual Task GetAsync(string providerName, string providerKey) + public virtual Task GetAsync(string providerName, string providerKey) { return FeatureAppService.GetAsync(providerName, providerKey); } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml index f5adbf79d4..601926b193 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml @@ -4,7 +4,6 @@ @using Volo.Abp.FeatureManagement.Localization @using Volo.Abp.Validation.StringValues @using Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement -@using Volo.Abp.Features @model FeatureManagementModal @inject IHtmlLocalizer L @{ @@ -13,53 +12,67 @@
    - @if (Model.FeatureListDto?.Features != null && Model.FeatureListDto.Features.Any()) + @if (Model.FeatureListResultDto != null && Model.FeatureListResultDto.Groups.Any()) { - - - @for (var i = 0; i < Model.FeatureListDto.Features.Count; i++) - { - var feature = Model.FeatureListDto.Features[i]; - var disabled = Model.IsDisabled(feature.Provider.Name); -
    + + + + @for (var i = 0; i < Model.FeatureListResultDto.Groups.Count; i++) + { + +

    @Model.FeatureListResultDto.Groups[i].DisplayName

    +
    +
    +
    + @for (var j = 0; j < Model.FeatureListResultDto.Groups[i].Features.Count; j++) + { + var feature = Model.FeatureListResultDto.Groups[i].Features[j]; + var disabled = Model.IsDisabled(feature.Provider.Name); +
    - @feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "") + @feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "") - - - @if (feature.ValueType is FreeTextStringValueType) - { - - - } - @if (feature.ValueType is SelectionStringValueType) - { - - + + @if (feature.ValueType is FreeTextStringValueType) + { + + + } + @if (feature.ValueType is SelectionStringValueType) + { + + + } + @if (feature.ValueType is ToggleStringValueType) + { + + + } +
    } - } - - } - @if (feature.ValueType is ToggleStringValueType) - { - - - } -
    - } +
    +
    + + } + +
    - + } else { @@ -69,4 +82,3 @@ }
    - diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs index e46d686e69..1464a1fc9b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs @@ -22,9 +22,9 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement public string ProviderKey { get; set; } [BindProperty] - public List Features { get; set; } + public List FeatureGroups { get; set; } - public FeatureListDto FeatureListDto { get; set; } + public GetFeatureListResultDto FeatureListResultDto { get; set; } protected IFeatureAppService FeatureAppService { get; } @@ -35,16 +35,20 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement FeatureAppService = featureAppService; } - public virtual async Task OnGetAsync() + public virtual async Task OnGetAsync() { - FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + ValidateModel(); + + FeatureListResultDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + + return Page(); } public virtual async Task OnPostAsync() { var features = new UpdateFeaturesDto { - Features = Features.Select(f => new UpdateFeatureDto + Features = FeatureGroups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto { Name = f.Name, Value = f.Type == nameof(ToggleStringValueType) ? f.BoolValue.ToString() : f.Value @@ -68,6 +72,11 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement public string ProviderKey { get; set; } } + public class FeatureGroupViewModel + { + public List Features { get; set; } + } + public class FeatureViewModel { public string Name { get; set; } diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs index b257f873cd..2f904a46d2 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.FeatureManagement TestEditionIds.Regular.ToString()); featureList.ShouldNotBeNull(); - featureList.Features.ShouldContain(feature => feature.Name == TestFeatureDefinitionProvider.SocialLogins); + featureList.Groups.SelectMany(g =>g .Features).ShouldContain(feature => feature.Name == TestFeatureDefinitionProvider.SocialLogins); } [Fact] @@ -63,7 +63,7 @@ namespace Volo.Abp.FeatureManagement }); (await _featureAppService.GetAsync(EditionFeatureValueProvider.ProviderName, - TestEditionIds.Regular.ToString())).Features.Any(x => + TestEditionIds.Regular.ToString())).Groups.SelectMany(g => g.Features).Any(x => x.Name == TestFeatureDefinitionProvider.SocialLogins && x.Value == false.ToString().ToLowerInvariant()) .ShouldBeTrue(); diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs index 528e0de5e8..1b7553b773 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs @@ -20,38 +20,46 @@ namespace Volo.Abp.FeatureManagement [Fact] public void Should_Serialize_And_Deserialize() { - var featureListDto = new FeatureListDto + var featureListDto = new GetFeatureListResultDto { - Features = new List + Groups = new List { - new FeatureDto + new FeatureGroupDto { - ValueType = new FreeTextStringValueType + Name = "MyGroup", + DisplayName = "MyGroup", + Features = new List { - Validator = new BooleanValueValidator() - } - }, - new FeatureDto - { - ValueType = new SelectionStringValueType - { - ItemSource = new StaticSelectionStringValueItemSource( - new LocalizableSelectionStringValueItem + new FeatureDto + { + ValueType = new FreeTextStringValueType { - Value = "TestValue", - DisplayText = new LocalizableStringInfo("TestResourceName", "TestName") - }), - Validator = new AlwaysValidValueValidator() - } - }, - new FeatureDto - { - ValueType = new ToggleStringValueType - { - Validator = new NumericValueValidator + Validator = new BooleanValueValidator() + } + }, + new FeatureDto { - MaxValue = 1000, - MinValue = 10 + ValueType = new SelectionStringValueType + { + ItemSource = new StaticSelectionStringValueItemSource( + new LocalizableSelectionStringValueItem + { + Value = "TestValue", + DisplayText = new LocalizableStringInfo("TestResourceName", "TestName") + }), + Validator = new AlwaysValidValueValidator() + } + }, + new FeatureDto + { + ValueType = new ToggleStringValueType + { + Validator = new NumericValueValidator + { + MaxValue = 1000, + MinValue = 10 + } + } } } } @@ -59,22 +67,22 @@ namespace Volo.Abp.FeatureManagement }; var serialized = _jsonSerializer.Serialize(featureListDto, indented: true); - var featureListDto2 = _jsonSerializer.Deserialize(serialized); + var featureListDto2 = _jsonSerializer.Deserialize(serialized); - featureListDto2.Features[0].ValueType.ShouldBeOfType(); - featureListDto2.Features[0].ValueType.Validator.ShouldBeOfType(); + featureListDto2.Groups[0].Features[0].ValueType.ShouldBeOfType(); + featureListDto2.Groups[0].Features[0].ValueType.Validator.ShouldBeOfType(); - featureListDto2.Features[1].ValueType.ShouldBeOfType(); - featureListDto2.Features[1].ValueType.Validator.ShouldBeOfType(); - featureListDto2.Features[1].ValueType.As().ItemSource.Items.ShouldBeOfType(); - featureListDto2.Features[1].ValueType.As().ItemSource.Items.ShouldContain(x => + featureListDto2.Groups[0].Features[1].ValueType.ShouldBeOfType(); + featureListDto2.Groups[0].Features[1].ValueType.Validator.ShouldBeOfType(); + featureListDto2.Groups[0].Features[1].ValueType.As().ItemSource.Items.ShouldBeOfType(); + featureListDto2.Groups[0].Features[1].ValueType.As().ItemSource.Items.ShouldContain(x => x.Value == "TestValue" && x.DisplayText.ResourceName == "TestResourceName" && x.DisplayText.Name == "TestName"); - featureListDto2.Features[2].ValueType.ShouldBeOfType(); - featureListDto2.Features[2].ValueType.Validator.ShouldBeOfType(); - featureListDto2.Features[2].ValueType.Validator.As().MaxValue.ShouldBe(1000); - featureListDto2.Features[2].ValueType.Validator.As().MinValue.ShouldBe(10); + featureListDto2.Groups[0].Features[2].ValueType.ShouldBeOfType(); + featureListDto2.Groups[0].Features[2].ValueType.Validator.ShouldBeOfType(); + featureListDto2.Groups[0].Features[2].ValueType.Validator.As().MaxValue.ShouldBe(1000); + featureListDto2.Groups[0].Features[2].ValueType.Validator.As().MinValue.ShouldBe(10); } } } From 2104c86eab18ce32a0f74ecd6d3356f4d21e9c3b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 11:07:37 +0300 Subject: [PATCH 44/69] chore: remove console.log --- .../packages/core/src/lib/states/replaceable-components.state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index e5caa9f95b..61a5fef5f6 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -56,7 +56,6 @@ export class ReplaceableComponentsState { replaceableComponents, }); - console.log(this.service); this.service.add(payload, reload); } } From 8fdbab9576574b37b3a112e965c91fe0fe64da09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 11:09:28 +0300 Subject: [PATCH 45/69] Completed the Features document. --- docs/en/Features.md | 67 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 05712eb803..1ec35f730a 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -176,6 +176,8 @@ namespace FeaturesDemo * First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. * Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. +Default value is used if there is no other value set for the current user/tenant. + ### Other Feature Properties While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; @@ -376,11 +378,70 @@ public class MyService : ITransientDependency ## Advanced Topics -TODO - ### Feature Value Providers -TODO +Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature. + +Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed. + +There are three pre-defined value providers, executed by the given order: + +* `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**. +* `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial. +* `DefaultValueFeatureValueProvider` gets the default value of the feature. + +You can write your own provider by inheriting the `FeatureValueProvider`. + +**Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value** + +```csharp +using System.Threading.Tasks; +using Volo.Abp.Features; +using Volo.Abp.Security.Claims; +using Volo.Abp.Validation.StringValues; + +namespace FeaturesDemo +{ + public class SystemAdminFeatureValueProvider : FeatureValueProvider + { + public override string Name => "SA"; + + private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; + + public SystemAdminFeatureValueProvider( + IFeatureStore featureStore, + ICurrentPrincipalAccessor currentPrincipalAccessor) + : base(featureStore) + { + _currentPrincipalAccessor = currentPrincipalAccessor; + } + + public override Task GetOrNullAsync(FeatureDefinition feature) + { + if (feature.ValueType is ToggleStringValueType && + _currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") + { + return Task.FromResult("true"); + } + + return null; + } + } +} +``` + +If a provider returns `null`, then the next provider is executed. + +Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below: + +```csharp +Configure(options => +{ + options.ValueProviders.Add(); +}); +``` + +Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class. ### Feature Store From 311287cce9b522f326e16e6138b378b020884251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 11:14:48 +0300 Subject: [PATCH 46/69] Added Features to the nav menu --- docs/en/Multi-Tenancy.md | 3 +++ docs/en/docs-nav.json | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 901a5c5cff..5138c9de04 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -369,3 +369,6 @@ options.AddDomainTenantResolver("{0}.mydomain.com"); options.AddDomainTenantResolver("{0}.com"); ```` +## See Also + +* [Features](Features.md) \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index dc872a2279..654bc720e2 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -170,6 +170,10 @@ "text": "Settings", "path": "Settings.md" }, + { + "text": "Features", + "path": "Features.md" + }, { "text": "Data Filtering", "path": "Data-Filtering.md" From 89df9fdde0a3f5a33ae6c4c01001ad4b4469c42f Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 12:05:54 +0300 Subject: [PATCH 47/69] test: fix testing errors --- ...laceable-route-container.component.spec.ts | 18 +++++++-------- .../replaceable-template.directive.spec.ts | 22 +++++++++++-------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index f5874bca46..16973c17c8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -5,6 +5,7 @@ import { Store } from '@ngxs/store'; import { of, Subject, BehaviorSubject } from 'rxjs'; import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component'; import { ReplaceableComponentsState } from '../states'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; @Component({ selector: 'abp-external-component', @@ -30,16 +31,15 @@ const activatedRouteMock = { }; describe('ReplaceableRouteContainerComponent', () => { - const selectResponse = new BehaviorSubject(undefined); - const mockSelect = jest.fn(() => selectResponse); - let spectator: SpectatorHost; + const get$Res = new BehaviorSubject(undefined); + const replaceableComponents = spectator.inject(ReplaceableComponentsService); + const spy = jest.spyOn(replaceableComponents, 'get$'); + spy.mockReturnValue(get$Res as any); + const createHost = createHostFactory({ component: ReplaceableRouteContainerComponent, - providers: [ - { provide: ActivatedRoute, useValue: activatedRouteMock }, - { provide: Store, useValue: { select: mockSelect } }, - ], + providers: [{ provide: ActivatedRoute, useValue: activatedRouteMock }], declarations: [ExternalComponent, DefaultComponent], entryComponents: [DefaultComponent, ExternalComponent], }); @@ -55,11 +55,11 @@ describe('ReplaceableRouteContainerComponent', () => { }); it("should display the external component if it's available in store.", () => { - selectResponse.next({ component: ExternalComponent }); + get$Res.next({ component: ExternalComponent }); spectator.detectChanges(); expect(spectator.query('p')).toHaveText('external'); - selectResponse.next({ component: null }); + get$Res.next({ component: null }); spectator.detectChanges(); expect(spectator.query('p')).toHaveText('default'); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 613508f56b..2923dd473b 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -4,6 +4,8 @@ import { Store } from '@ngxs/store'; import { Subject } from 'rxjs'; import { ReplaceableTemplateDirective } from '../directives'; import { ReplaceableComponents } from '../models'; +import { Router } from '@angular/router'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; @Component({ selector: 'abp-default-component', @@ -48,15 +50,17 @@ class ExternalComponent { } describe('ReplaceableTemplateDirective', () => { - const selectResponse = new Subject(); - const mockSelect = jest.fn(() => selectResponse); - let spectator: SpectatorDirective; + const get$Res = new Subject(); + + const replaceableComponents = spectator.inject(ReplaceableComponentsService); + const spy = jest.spyOn(replaceableComponents, 'get$'); + spy.mockReturnValue(get$Res as any); const createDirective = createDirectiveFactory({ directive: ReplaceableTemplateDirective, - providers: [{ provide: Store, useValue: { select: mockSelect } }], declarations: [DefaultComponent, ExternalComponent], entryComponents: [ExternalComponent], + mocks: [Router], }); describe('without external component', () => { @@ -72,7 +76,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); - selectResponse.next(undefined); + get$Res.next(undefined); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); spectator.detectChanges(); @@ -114,7 +118,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); - selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); }); afterEach(() => twoWayChange.mockClear()); @@ -150,7 +154,7 @@ describe('ReplaceableTemplateDirective', () => { const externalComponent = spectator.query(ExternalComponent); spectator.setHostInput({ oneWay: 'test' }); externalComponent.data.inputs.twoWay = true; - selectResponse.next({ component: null, key: 'TestModule.TestComponent' }); + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); spectator.detectChanges(); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); @@ -161,14 +165,14 @@ describe('ReplaceableTemplateDirective', () => { }); it('should reset default component subscriptions', () => { - selectResponse.next({ component: null, key: 'TestModule.TestComponent' }); + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); spectator.detectChanges(); const unsubscribe = jest.fn(() => {}); spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; - selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); expect(unsubscribe).toHaveBeenCalled(); }); }); From f5b6ef2388250eb4c742254ff9b0b0331299702d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 8 Sep 2020 18:06:58 +0800 Subject: [PATCH 48/69] add paged --- .../Volo/Abp/Identity/IOrganizationUnitRepository.cs | 8 ++++++-- .../EfCoreOrganizationUnitRepository.cs | 10 ++++++++-- .../MongoDB/MongoOrganizationUnitRepository.cs | 10 ++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs index 81474425db..4d5bf7d060 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs @@ -57,8 +57,10 @@ namespace Volo.Abp.Identity Task> GetUnaddedRolesAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default ); @@ -81,8 +83,10 @@ namespace Volo.Abp.Identity Task> GetUnaddedUsersAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default ); diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index d50745ab1d..ebfa9b8ebb 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -114,8 +114,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore public virtual async Task> GetUnaddedRolesAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default) { @@ -126,6 +128,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore .IncludeDetails(includeDetails) .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) .OrderBy(sorting ?? nameof(IdentityRole.Name)) + .PageBy(skipCount, maxResultCount) .ToListAsync(cancellationToken); } @@ -157,8 +160,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore public virtual async Task> GetUnaddedUsersAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default) { @@ -181,6 +186,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore return await query .IncludeDetails(includeDetails) .OrderBy(sorting ?? nameof(IdentityUser.Name)) + .PageBy(skipCount, maxResultCount) .ToListAsync(cancellationToken); } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 2823c99a6c..2b28a2e3a1 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -109,8 +109,10 @@ namespace Volo.Abp.Identity.MongoDB public async Task> GetUnaddedRolesAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default) { @@ -120,6 +122,7 @@ namespace Volo.Abp.Identity.MongoDB .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) .OrderBy(sorting ?? nameof(IdentityRole.Name)) .As>() + .PageBy>(skipCount, maxResultCount) .ToListAsync(cancellationToken); } @@ -153,8 +156,10 @@ namespace Volo.Abp.Identity.MongoDB public async Task> GetUnaddedUsersAsync( OrganizationUnit organizationUnit, - string filter = null, string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default) { @@ -169,6 +174,7 @@ namespace Volo.Abp.Identity.MongoDB ) .OrderBy(sorting ?? nameof(IdentityUser.UserName)) .As>() + .PageBy>(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } From 59a6c630be8f9de8cbadbe68f8c328fa55de37d8 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 8 Sep 2020 18:33:59 +0800 Subject: [PATCH 49/69] Add get count method --- .../Identity/IOrganizationUnitRepository.cs | 12 +++++++ .../EfCoreOrganizationUnitRepository.cs | 31 +++++++++++++++++++ .../MongoOrganizationUnitRepository.cs | 29 +++++++++++++++++ .../OrganizationUnitRepository_Tests.cs | 17 ++++++++++ 4 files changed, 89 insertions(+) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs index 4d5bf7d060..bf39874e00 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs @@ -65,6 +65,12 @@ namespace Volo.Abp.Identity CancellationToken cancellationToken = default ); + Task GetUnaddedRolesCountAsync( + OrganizationUnit organizationUnit, + string filter = null, + CancellationToken cancellationToken = default + ); + Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -91,6 +97,12 @@ namespace Volo.Abp.Identity CancellationToken cancellationToken = default ); + Task GetUnaddedUsersCountAsync( + OrganizationUnit organizationUnit, + string filter = null, + CancellationToken cancellationToken = default + ); + Task RemoveAllRolesAsync( OrganizationUnit organizationUnit, CancellationToken cancellationToken = default diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index ebfa9b8ebb..59fbd9518e 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -132,6 +132,19 @@ namespace Volo.Abp.Identity.EntityFrameworkCore .ToListAsync(cancellationToken); } + public virtual async Task GetUnaddedRolesCountAsync( + OrganizationUnit organizationUnit, + string filter = null, + CancellationToken cancellationToken = default) + { + var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToList(); + + return await DbContext.Roles + .Where(r => !roleIds.Contains(r.Id)) + .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) + .CountAsync(cancellationToken); + } + public virtual async Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -190,6 +203,24 @@ namespace Volo.Abp.Identity.EntityFrameworkCore .ToListAsync(cancellationToken); } + public virtual async Task GetUnaddedUsersCountAsync( + OrganizationUnit organizationUnit, + string filter = null, + CancellationToken cancellationToken = default) + { + var userIdsInOrganizationUnit = DbContext.Set() + .Where(uou => uou.OrganizationUnitId == organizationUnit.Id) + .Select(uou => uou.UserId); + + return await DbContext.Users + .Where(u => !userIdsInOrganizationUnit.Contains(u.Id)) + .WhereIf(!filter.IsNullOrWhiteSpace(), u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) || + (u.PhoneNumber != null && u.PhoneNumber.Contains(filter))) + .CountAsync(cancellationToken); + } + public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 2b28a2e3a1..10ae4cd038 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -126,6 +126,19 @@ namespace Volo.Abp.Identity.MongoDB .ToListAsync(cancellationToken); } + public async Task GetUnaddedRolesCountAsync( + OrganizationUnit organizationUnit, + string filter = null, + CancellationToken cancellationToken = default) + { + var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); + return await DbContext.Roles.AsQueryable() + .Where(r => !roleIds.Contains(r.Id)) + .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) + .As>() + .CountAsync(cancellationToken); + } + public virtual async Task> GetMembersAsync( OrganizationUnit organizationUnit, string sorting = null, @@ -178,6 +191,22 @@ namespace Volo.Abp.Identity.MongoDB .ToListAsync(GetCancellationToken(cancellationToken)); } + public async Task GetUnaddedUsersCountAsync(OrganizationUnit organizationUnit, string filter = null, + CancellationToken cancellationToken = default) + { + return await DbContext.Users.AsQueryable() + .Where(u => !u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) || + (u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) + ) + .As>() + .CountAsync(GetCancellationToken(cancellationToken)); + } + public virtual Task RemoveAllRolesAsync(OrganizationUnit organizationUnit, CancellationToken cancellationToken = default) { organizationUnit.Roles.Clear(); diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs index 97544cd1e9..a671ba3284 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs @@ -305,5 +305,22 @@ namespace Volo.Abp.Identity unaddedRoles.ShouldNotContain(u => u.Name == "moderator"); unaddedRoles.ShouldContain(u => u.Name.Contains("admin")); } + + [Fact] + public async Task GetUnaddedUsersCountOfOrganizationUnitAsync() + { + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + var count = await _organizationUnitRepository.GetUnaddedUsersCountAsync(ou); + count.ShouldBeGreaterThan(0); + + } + + [Fact] + public async Task GetUnaddedRolesCountOfOrganizationUnitAsync() + { + var ou = await _organizationUnitRepository.GetAsync("OU111", true); + var count = await _organizationUnitRepository.GetUnaddedRolesCountAsync(ou); + count.ShouldBeGreaterThan(0); + } } } From 431d02f177153c79ae6330a45c8c27de3d95a1fa Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 14:26:45 +0300 Subject: [PATCH 50/69] chore: add deprecation message logger to replaceable-components.state --- .../lib/states/replaceable-components.state.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 61a5fef5f6..8b861f144e 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,10 +1,19 @@ -import { Injectable } from '@angular/core'; +import { Injectable, isDevMode } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; import snq from 'snq'; import { AddReplaceableComponent } from '../actions/replaceable-components.actions'; import { ReplaceableComponents } from '../models/replaceable-components'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +function logDeprecationMsg() { + if (isDevMode()) { + console.warn(` + ReplacableComponentsState has been deprecated. Use ReplaceableComponentsService instead. + See the doc https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement + `); + } +} + // tslint:disable: max-line-length /** * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) @@ -19,6 +28,7 @@ export class ReplaceableComponentsState { static getAll({ replaceableComponents, }: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent[] { + logDeprecationMsg(); return replaceableComponents || []; } @@ -26,6 +36,7 @@ export class ReplaceableComponentsState { const selector = createSelector( [ReplaceableComponentsState], (state: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent => { + logDeprecationMsg(); return snq(() => state.replaceableComponents.find(component => component.key === key)); }, ); @@ -40,6 +51,8 @@ export class ReplaceableComponentsState { { getState, patchState }: StateContext, { payload, reload }: AddReplaceableComponent, ) { + logDeprecationMsg(); + let { replaceableComponents } = getState(); const index = snq( From d43dc102c76e0f34d2ce2341e9774309ff004c01 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 8 Sep 2020 15:30:40 +0300 Subject: [PATCH 51/69] Update Default.cshtml --- .../Pages/CmsKit/Shared/Components/Commenting/Default.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml index ab7fc9805b..f988dd29e7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml @@ -175,7 +175,7 @@
    } - @if (comment.Replies.Count >= 5) + @if (comment.Replies.Count >= 3) {
    @if (CurrentUser.IsAuthenticated) From cd91017415ab506d2cd0dafed9f047b4caa2ce38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87otur?= Date: Tue, 8 Sep 2020 17:31:32 +0300 Subject: [PATCH 52/69] Update AbpComponentDemoSectionTagHelper.cs --- .../Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs index 722460992d..1f1f71b066 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs @@ -47,7 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S output.PreContent.AppendHtml($"

    {Title}

    "); output.PreContent.AppendHtml("
    "); output.PreContent.AppendHtml("