Browse Source

Merge branch 'cms-kit/blog-initial' into cotur/cms-kit/media

pull/7685/head
Ahmet 6 years ago
parent
commit
019dbceb85
  1. 2
      modules/cms-kit/host/Volo.CmsKit.Host.Shared/FeatureConfigurer.cs
  2. 1891
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20210209082122_UrlSlugToSlug.Designer.cs
  3. 106
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20210209082122_UrlSlugToSlug.cs
  4. 168
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs
  5. 4
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogDto.cs
  6. 9
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogGetListInput.cs
  7. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogPostDto.cs
  8. 6
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/CreateUpdateBlogPostDto.cs
  9. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/IBlogAdminAppService.cs
  10. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/IBlogPostAdminAppService.cs
  11. 13
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogAdminAppService.cs
  12. 12
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs
  13. 10
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs
  14. 6
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogPostAdminController.cs
  15. 2
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogConsts.cs
  16. 4
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs
  17. 2
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs
  18. 3
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj
  19. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Blog.cs
  20. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPost.cs
  21. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostManager.cs
  22. 31
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostSlugAlreadyExistException.cs
  23. 31
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostUrlSlugAlreadyExistException.cs
  24. 15
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Extensions/SlugExtensions.cs
  25. 30
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Extensions/UrlSlugExtensions.cs
  26. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs
  27. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogRepository.cs
  28. 6
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs
  29. 4
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogRepository.cs
  30. 6
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs
  31. 6
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs
  32. 4
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs
  33. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs
  34. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs
  35. 10
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  36. 8
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs
  37. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs
  38. 34
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/PageController.cs
  39. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/BlogPost.cshtml
  40. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/BlogPost.cshtml.cs
  41. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/Index.cshtml
  42. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/Index.cshtml.cs
  43. 13
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml
  44. 34
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs
  45. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml
  46. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/DefaultBlogPostViewComponent.cs
  47. 9
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Views/Page/Index.cshtml
  48. 42
      modules/cms-kit/test/Volo.CmsKit.Application.Tests/Blogs/BlogPostAdminAppService_Tests.cs
  49. 28
      modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Blogs/BlogPostManager_Tests.cs
  50. 45
      modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Blogs/SlugExtensions_Tests.cs
  51. 22
      modules/cms-kit/test/Volo.CmsKit.TestBase/Blogs/BlogPostRepository_Test.cs
  52. 10
      modules/cms-kit/test/Volo.CmsKit.TestBase/Blogs/BlogRepository_Test.cs
  53. 6
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs
  54. 6
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs

2
modules/cms-kit/host/Volo.CmsKit.Host.Shared/FeatureConfigurer.cs

@ -1,5 +1,6 @@
using Volo.Abp.GlobalFeatures; using Volo.Abp.GlobalFeatures;
using Volo.Abp.Threading; using Volo.Abp.Threading;
using Volo.CmsKit.GlobalFeatures;
namespace Volo.CmsKit namespace Volo.CmsKit
{ {
@ -12,6 +13,7 @@ namespace Volo.CmsKit
OneTimeRunner.Run(() => OneTimeRunner.Run(() =>
{ {
GlobalFeatureManager.Instance.Modules.CmsKit().EnableAll(); GlobalFeatureManager.Instance.Modules.CmsKit().EnableAll();
GlobalFeatureManager.Instance.Modules.CmsKit().Disable<PagesFeature>();
}); });
} }
} }

1891
modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20210209082122_UrlSlugToSlug.Designer.cs

File diff suppressed because it is too large

106
modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/20210209082122_UrlSlugToSlug.cs

@ -0,0 +1,106 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Volo.CmsKit.Migrations
{
public partial class UrlSlugToSlug : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CmsBlogPosts",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BlogId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Title = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Slug = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
ShortDescription = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsBlogPosts", x => x.Id);
table.ForeignKey(
name: "FK_CmsBlogPosts_CmsUsers_CreatorId",
column: x => x.CreatorId,
principalTable: "CmsUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_CmsBlogPosts_CmsUsers_DeleterId",
column: x => x.DeleterId,
principalTable: "CmsUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_CmsBlogPosts_CmsUsers_LastModifierId",
column: x => x.LastModifierId,
principalTable: "CmsUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "CmsBlogs",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Slug = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsBlogs", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_CreatorId",
table: "CmsBlogPosts",
column: "CreatorId");
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_DeleterId",
table: "CmsBlogPosts",
column: "DeleterId");
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_LastModifierId",
table: "CmsBlogPosts",
column: "LastModifierId");
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_Slug_BlogId",
table: "CmsBlogPosts",
columns: new[] { "Slug", "BlogId" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CmsBlogPosts");
migrationBuilder.DropTable(
name: "CmsBlogs");
}
}
}

168
modules/cms-kit/host/Volo.CmsKit.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs

@ -19,7 +19,7 @@ namespace Volo.CmsKit.Migrations
.UseIdentityColumns() .UseIdentityColumns()
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.1"); .HasAnnotation("ProductVersion", "5.0.2");
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
{ {
@ -1124,6 +1124,151 @@ namespace Volo.CmsKit.Migrations
b.ToTable("AbpTenantConnectionStrings"); b.ToTable("AbpTenantConnectionStrings");
}); });
modelBuilder.Entity("Volo.CmsKit.Blogs.Blog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.ToTable("CmsBlogs");
});
modelBuilder.Entity("Volo.CmsKit.Blogs.BlogPost", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("BlogId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("ShortDescription")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("Id");
b.HasIndex("CreatorId");
b.HasIndex("DeleterId");
b.HasIndex("LastModifierId");
b.HasIndex("Slug", "BlogId");
b.ToTable("CmsBlogPosts");
});
modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b => modelBuilder.Entity("Volo.CmsKit.Comments.Comment", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -1678,6 +1823,27 @@ namespace Volo.CmsKit.Migrations
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("Volo.CmsKit.Blogs.BlogPost", b =>
{
b.HasOne("Volo.CmsKit.Users.CmsUser", "Creator")
.WithMany()
.HasForeignKey("CreatorId");
b.HasOne("Volo.CmsKit.Users.CmsUser", "Deleter")
.WithMany()
.HasForeignKey("DeleterId");
b.HasOne("Volo.CmsKit.Users.CmsUser", "LastModifier")
.WithMany()
.HasForeignKey("LastModifierId");
b.Navigation("Creator");
b.Navigation("Deleter");
b.Navigation("LastModifier");
});
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
{ {
b.Navigation("Actions"); b.Navigation("Actions");

4
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogDto.cs

@ -13,7 +13,7 @@ namespace Volo.CmsKit.Admin.Blogs
public string Name { get; set; } public string Name { get; set; }
[Required] [Required]
[DynamicMaxLength(typeof(BlogConsts), nameof(BlogConsts.MaxUrlSlugLength))] [DynamicMaxLength(typeof(BlogConsts), nameof(BlogConsts.MaxSlugLength))]
public string UrlSlug { get; set; } public string Slug { get; set; }
} }
} }

9
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogGetListInput.cs

@ -0,0 +1,9 @@
using Volo.Abp.Application.Dtos;
namespace Volo.CmsKit.Admin.Blogs
{
public class BlogGetListInput : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
}
}

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/BlogPostDto.cs

@ -9,7 +9,7 @@ namespace Volo.CmsKit.Admin.Blogs
public string Title { get; set; } public string Title { get; set; }
public string UrlSlug { get; set; } public string Slug { get; set; }
public string ShortDescription { get; protected set; } public string ShortDescription { get; protected set; }
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/CreateUpdateBlogPostDto.cs

@ -17,9 +17,9 @@ namespace Volo.CmsKit.Admin.Blogs
[Required] [Required]
[DynamicStringLength( [DynamicStringLength(
typeof(BlogPostConsts), typeof(BlogPostConsts),
nameof(BlogPostConsts.MaxUrlSlugLength), nameof(BlogPostConsts.MaxSlugLength),
nameof(BlogPostConsts.MinUrlSlugLength))] nameof(BlogPostConsts.MinSlugLength))]
public string UrlSlug { get; set; } public string Slug { get; set; }
[DynamicMaxLength(typeof(BlogPostConsts), nameof(BlogPostConsts.MaxShortDescriptionLength))] [DynamicMaxLength(typeof(BlogPostConsts), nameof(BlogPostConsts.MaxShortDescriptionLength))]
public string ShortDescription { get; set; } public string ShortDescription { get; set; }

3
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/IBlogAdminAppService.cs

@ -5,8 +5,7 @@ using Volo.Abp.Application.Services;
namespace Volo.CmsKit.Admin.Blogs namespace Volo.CmsKit.Admin.Blogs
{ {
public interface IBlogAdminAppService : ICrudAppService<BlogDto, Guid> public interface IBlogAdminAppService : ICrudAppService<BlogDto, Guid, BlogGetListInput>
{ {
Task<List<BlogLookupDto>> GetLookupAsync();
} }
} }

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Blogs/IBlogPostAdminAppService.cs

@ -13,7 +13,7 @@ namespace Volo.CmsKit.Admin.Blogs
PagedAndSortedResultRequestDto, PagedAndSortedResultRequestDto,
CreateUpdateBlogPostDto> CreateUpdateBlogPostDto>
{ {
Task<BlogPostDto> GetByUrlSlugAsync(string blogUrlSlug, string urlSlug); Task<BlogPostDto> GetBySlugAsync(string blogSlug, string slug);
Task SetCoverImageAsync(Guid id, RemoteStreamContent streamContent); Task SetCoverImageAsync(Guid id, RemoteStreamContent streamContent);

13
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogAdminAppService.cs

@ -14,7 +14,7 @@ namespace Volo.CmsKit.Admin.Blogs
{ {
[RequiresGlobalFeature(typeof(BlogsFeature))] [RequiresGlobalFeature(typeof(BlogsFeature))]
[Authorize(CmsKitAdminPermissions.Blogs.Default)] [Authorize(CmsKitAdminPermissions.Blogs.Default)]
public class BlogAdminAppService : CrudAppService<Blog, BlogDto, Guid>, IBlogAdminAppService public class BlogAdminAppService : CrudAppService<Blog, BlogDto, Guid, BlogGetListInput>, IBlogAdminAppService
{ {
public BlogAdminAppService(IRepository<Blog, Guid> repository) : base(repository) public BlogAdminAppService(IRepository<Blog, Guid> repository) : base(repository)
{ {
@ -25,12 +25,13 @@ namespace Volo.CmsKit.Admin.Blogs
DeletePolicyName = CmsKitAdminPermissions.Blogs.Delete; DeletePolicyName = CmsKitAdminPermissions.Blogs.Delete;
} }
[Authorize(CmsKitAdminPermissions.Blogs.Default)] protected override async Task<IQueryable<Blog>> CreateFilteredQueryAsync(BlogGetListInput input)
public async Task<List<BlogLookupDto>> GetLookupAsync()
{ {
var blogs = await Repository.GetListAsync(); var queryable = await base.CreateFilteredQueryAsync(input);
return queryable
return ObjectMapper.Map<List<Blog>, List<BlogLookupDto>>(blogs); .WhereIf(
!input.Filter.IsNullOrWhiteSpace(),
x => x.Name.ToLower().Contains(input.Filter));
} }
} }
} }

12
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs

@ -53,11 +53,11 @@ namespace Volo.CmsKit.Admin.Blogs
DeletePolicyName = CmsKitAdminPermissions.BlogPosts.Delete; DeletePolicyName = CmsKitAdminPermissions.BlogPosts.Delete;
} }
public virtual async Task<BlogPostDto> GetByUrlSlugAsync(string blogUrlSlug, string urlSlug) public virtual async Task<BlogPostDto> GetBySlugAsync(string blogSlug, string blogPostSlug)
{ {
var blog = await BlogRepository.GetByUrlSlugAsync(blogUrlSlug); var blog = await BlogRepository.GetBySlugAsync(blogSlug);
var blogPost = await BlogPostRepository.GetByUrlSlugAsync(blog.Id, urlSlug); var blogPost = await BlogPostRepository.GetBySlugAsync(blog.Id, blogPostSlug);
return MapToGetOutputDto(blogPost); return MapToGetOutputDto(blogPost);
} }
@ -73,7 +73,7 @@ namespace Volo.CmsKit.Admin.Blogs
GuidGenerator.Create(), GuidGenerator.Create(),
input.BlogId, input.BlogId,
input.Title, input.Title,
input.UrlSlug, input.Slug,
input.ShortDescription)); input.ShortDescription));
return MapToGetOutputDto(entity); return MapToGetOutputDto(entity);
@ -86,9 +86,9 @@ namespace Volo.CmsKit.Admin.Blogs
blogPost.SetTitle(input.Title); blogPost.SetTitle(input.Title);
if (blogPost.UrlSlug != input.UrlSlug) if (blogPost.Slug != input.Slug)
{ {
await BlogPostManager.SetSlugUrlAsync(blogPost, input.UrlSlug); await BlogPostManager.SetSlugUrlAsync(blogPost, input.Slug);
} }
MapToEntity(input, blogPost); MapToEntity(input, blogPost);

10
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs

@ -52,7 +52,7 @@ namespace Volo.CmsKit.Admin.Blogs
[HttpGet] [HttpGet]
[Authorize(CmsKitAdminPermissions.Blogs.Default)] [Authorize(CmsKitAdminPermissions.Blogs.Default)]
public Task<PagedResultDto<BlogDto>> GetListAsync([FromQuery] PagedAndSortedResultRequestDto input) public Task<PagedResultDto<BlogDto>> GetListAsync([FromQuery] BlogGetListInput input)
{ {
return BlogAdminAppService.GetListAsync(input); return BlogAdminAppService.GetListAsync(input);
} }
@ -64,13 +64,5 @@ namespace Volo.CmsKit.Admin.Blogs
{ {
return BlogAdminAppService.UpdateAsync(id, input); return BlogAdminAppService.UpdateAsync(id, input);
} }
[HttpGet]
[Route("lookup")]
[Authorize(CmsKitAdminPermissions.Blogs.Default)]
public Task<List<BlogLookupDto>> GetLookupAsync()
{
return BlogAdminAppService.GetLookupAsync();
}
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogPostAdminController.cs

@ -53,11 +53,11 @@ namespace Volo.CmsKit.Admin.Blogs
} }
[HttpGet] [HttpGet]
[Route("{blogUrlSlug}/{urlSlug}")] [Route("{blogSlug}/{blogPostSlug}")]
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] [Authorize(CmsKitAdminPermissions.BlogPosts.Default)]
public virtual Task<BlogPostDto> GetByUrlSlugAsync(string blogUrlSlug, string urlSlug) public virtual Task<BlogPostDto> GetBySlugAsync(string blogSlug, string blogPostSlug)
{ {
return BlogPostAdminAppService.GetByUrlSlugAsync(blogUrlSlug, urlSlug); return BlogPostAdminAppService.GetBySlugAsync(blogSlug, blogPostSlug);
} }
[HttpGet] [HttpGet]

2
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogConsts.cs

@ -3,6 +3,6 @@
public class BlogConsts public class BlogConsts
{ {
public static int MaxNameLength { get; set; } = 64; public static int MaxNameLength { get; set; } = 64;
public static int MaxUrlSlugLength { get; set; } = 64; public static int MaxSlugLength { get; set; } = 64;
} }
} }

4
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Blogs/BlogPostConsts.cs

@ -4,9 +4,9 @@
{ {
public static int MaxTitleLength { get; set; } = 64; public static int MaxTitleLength { get; set; } = 64;
public static int MaxUrlSlugLength { get; set; } = 256; public static int MaxSlugLength { get; set; } = 256;
public static int MinUrlSlugLength { get; set; } = 2; public static int MinSlugLength { get; set; } = 2;
public static int MaxShortDescriptionLength { get; set; } = 256; public static int MaxShortDescriptionLength { get; set; } = 256;

2
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs

@ -17,7 +17,7 @@
public static class Blogs public static class Blogs
{ {
public const string UrlSlugAlreadyExist = "CmsKit:BlogPost:0001"; public const string SlugAlreadyExist = "CmsKit:BlogPost:0001";
} }
public static class MediaDescriptors public static class MediaDescriptors

3
modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" /> <Import Project="..\..\..\..\common.props" />
<Import Project="..\..\..\..\configureawait.props" /> <Import Project="..\..\..\..\configureawait.props" />
@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Slugify.Core" Version="3.0.0" />
<PackageReference Include="Unidecode.NET" Version="2.1.0" /> <PackageReference Include="Unidecode.NET" Version="2.1.0" />
</ItemGroup> </ItemGroup>

12
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Blog.cs

@ -12,17 +12,17 @@ namespace Volo.CmsKit.Blogs
public Blog( public Blog(
Guid id, Guid id,
[NotNull] string name, [NotNull] string name,
[NotNull] string urlSlug, [NotNull] string slug,
[CanBeNull] Guid? tenantId = null) : base(id) [CanBeNull] Guid? tenantId = null) : base(id)
{ {
SetName(name); SetName(name);
SetUrlSlug(urlSlug); SetSlug(slug);
TenantId = tenantId; TenantId = tenantId;
} }
public string Name { get; protected set; } public string Name { get; protected set; }
public string UrlSlug { get; protected set; } public string Slug { get; protected set; }
public Guid? TenantId { get; protected set; } public Guid? TenantId { get; protected set; }
@ -31,11 +31,11 @@ namespace Volo.CmsKit.Blogs
Name = Check.NotNullOrWhiteSpace(name, nameof(name), maxLength: BlogConsts.MaxNameLength); Name = Check.NotNullOrWhiteSpace(name, nameof(name), maxLength: BlogConsts.MaxNameLength);
} }
public void SetUrlSlug(string urlSlug) public void SetSlug(string slug)
{ {
Check.NotNullOrWhiteSpace(urlSlug, nameof(urlSlug), maxLength: BlogConsts.MaxNameLength); Check.NotNullOrWhiteSpace(slug, nameof(slug), maxLength: BlogConsts.MaxNameLength);
UrlSlug = urlSlug.NormalizeAsUrlSlug(); Slug = slug.NormalizeSlug();
} }
} }
} }

12
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPost.cs

@ -16,7 +16,7 @@ namespace Volo.CmsKit.Blogs
public string Title { get; protected set; } public string Title { get; protected set; }
public string UrlSlug { get; protected set; } public string Slug { get; protected set; }
public string ShortDescription { get; protected set; } public string ShortDescription { get; protected set; }
@ -30,12 +30,12 @@ namespace Volo.CmsKit.Blogs
Guid id, Guid id,
Guid blogId, Guid blogId,
[NotNull] string title, [NotNull] string title,
[NotNull] string urlSlug, [NotNull] string slug,
[CanBeNull] string shortDescription = null) : base(id) [CanBeNull] string shortDescription = null) : base(id)
{ {
BlogId = blogId; BlogId = blogId;
SetTitle(title); SetTitle(title);
SetUrlSlug(urlSlug); SetSlug(slug);
ShortDescription = shortDescription; ShortDescription = shortDescription;
} }
@ -44,11 +44,11 @@ namespace Volo.CmsKit.Blogs
Title = Check.NotNullOrWhiteSpace(title, nameof(title), BlogPostConsts.MaxTitleLength); Title = Check.NotNullOrWhiteSpace(title, nameof(title), BlogPostConsts.MaxTitleLength);
} }
internal void SetUrlSlug(string urlSlug) internal void SetSlug(string slug)
{ {
Check.NotNullOrWhiteSpace(urlSlug, nameof(urlSlug), BlogPostConsts.MaxUrlSlugLength, BlogPostConsts.MinUrlSlugLength); Check.NotNullOrWhiteSpace(slug, nameof(slug), BlogPostConsts.MaxSlugLength, BlogPostConsts.MinSlugLength);
UrlSlug = urlSlug.NormalizeAsUrlSlug(); Slug = slug.NormalizeSlug();
} }
public void SetShortDescription(string shortDescription) public void SetShortDescription(string shortDescription)

12
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostManager.cs

@ -25,7 +25,7 @@ namespace Volo.CmsKit.Blogs
{ {
await CheckBlogExistenceAsync(blogPost.BlogId); await CheckBlogExistenceAsync(blogPost.BlogId);
await CheckUrlSlugExistenceAsync(blogPost.BlogId, blogPost.UrlSlug); await CheckSlugExistenceAsync(blogPost.BlogId, blogPost.Slug);
return await blogPostRepository.InsertAsync(blogPost); return await blogPostRepository.InsertAsync(blogPost);
} }
@ -39,16 +39,16 @@ namespace Volo.CmsKit.Blogs
public async Task SetSlugUrlAsync(BlogPost blogPost, string newSlug) public async Task SetSlugUrlAsync(BlogPost blogPost, string newSlug)
{ {
await CheckUrlSlugExistenceAsync(blogPost.BlogId, newSlug); await CheckSlugExistenceAsync(blogPost.BlogId, newSlug);
blogPost.SetUrlSlug(newSlug); blogPost.SetSlug(newSlug);
} }
private async Task CheckUrlSlugExistenceAsync(Guid blogId, string urlSlug) private async Task CheckSlugExistenceAsync(Guid blogId, string slug)
{ {
if (await blogPostRepository.SlugExistsAsync(blogId, urlSlug)) if (await blogPostRepository.SlugExistsAsync(blogId, slug))
{ {
throw new BlogPostUrlSlugAlreadyExistException(blogId, urlSlug); throw new BlogPostSlugAlreadyExistException(blogId, slug);
} }
} }

31
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostSlugAlreadyExistException.cs

@ -0,0 +1,31 @@
using System;
using Volo.Abp;
namespace Volo.CmsKit.Blogs
{
public class BlogPostSlugAlreadyExistException : BusinessException
{
internal BlogPostSlugAlreadyExistException(string code = null, string message = null, string details = null, Exception innerException = null, Microsoft.Extensions.Logging.LogLevel logLevel = Microsoft.Extensions.Logging.LogLevel.Warning) : base(code, message, details, innerException, logLevel)
{
}
internal BlogPostSlugAlreadyExistException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext context) : base(serializationInfo, context)
{
}
public BlogPostSlugAlreadyExistException(Guid blogId, string slug)
{
Slug = slug;
BlogId = blogId;
Code = CmsKitErrorCodes.Blogs.SlugAlreadyExist;
WithData(nameof(Slug), Slug);
WithData(nameof(BlogId), BlogId);
}
public string Slug { get; }
public Guid BlogId { get; }
}
}

31
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/BlogPostUrlSlugAlreadyExistException.cs

@ -1,31 +0,0 @@
using System;
using Volo.Abp;
namespace Volo.CmsKit.Blogs
{
public class BlogPostUrlSlugAlreadyExistException : BusinessException
{
internal BlogPostUrlSlugAlreadyExistException(string code = null, string message = null, string details = null, Exception innerException = null, Microsoft.Extensions.Logging.LogLevel logLevel = Microsoft.Extensions.Logging.LogLevel.Warning) : base(code, message, details, innerException, logLevel)
{
}
internal BlogPostUrlSlugAlreadyExistException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext context) : base(serializationInfo, context)
{
}
public BlogPostUrlSlugAlreadyExistException(Guid blogId, string urlSlug)
{
UrlSlug = urlSlug;
BlogId = blogId;
Code = CmsKitErrorCodes.Blogs.UrlSlugAlreadyExist;
WithData(nameof(UrlSlug), UrlSlug);
WithData(nameof(BlogId), BlogId);
}
public string UrlSlug { get; }
public Guid BlogId { get; }
}
}

15
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Extensions/SlugExtensions.cs

@ -0,0 +1,15 @@
using Slugify;
using Unidecode.NET;
namespace Volo.CmsKit.Blogs.Extensions
{
public static class SlugExtensions
{
public static string NormalizeSlug(this string value)
{
var slugHelper = new SlugHelper();
return slugHelper.GenerateSlug(value?.Unidecode());
}
}
}

30
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/Extensions/UrlSlugExtensions.cs

@ -1,30 +0,0 @@
using System.Text.RegularExpressions;
using Unidecode.NET;
namespace Volo.CmsKit.Blogs.Extensions
{
public static class UrlSlugExtensions
{
public static string NormalizeAsUrlSlug(this string value)
{
value = value.ToLowerInvariant();
// Unidecode for non-latin characters
value = value.Unidecode();
// Replace spaces
value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled);
// Remove invalid chars
value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled);
// Trim dashes & dots
value = value.Trim('-', '_', '.');
// Replace double occurences of - or _
value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled);
return value;
}
}
}

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs

@ -12,7 +12,7 @@ namespace Volo.CmsKit.Blogs
{ {
Task<bool> SlugExistsAsync(Guid blogId, string slug, CancellationToken cancellationToken = default); Task<bool> SlugExistsAsync(Guid blogId, string slug, CancellationToken cancellationToken = default);
Task<BlogPost> GetByUrlSlugAsync(Guid blogId, string urlSlug, CancellationToken cancellationToken = default); Task<BlogPost> GetBySlugAsync(Guid blogId, string Slug, CancellationToken cancellationToken = default);
Task<List<BlogPost>> GetPagedListAsync(Guid blogId, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default); Task<List<BlogPost>> GetPagedListAsync(Guid blogId, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default);

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogRepository.cs

@ -6,7 +6,7 @@ namespace Volo.CmsKit.Blogs
{ {
public interface IBlogRepository : IBasicRepository<Blog, Guid> public interface IBlogRepository : IBasicRepository<Blog, Guid>
{ {
public Task<Blog> GetByUrlSlugAsync(string urlSlug); public Task<Blog> GetBySlugAsync(string slug);
Task<bool> ExistsAsync(Guid blogId); Task<bool> ExistsAsync(Guid blogId);
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs

@ -20,14 +20,14 @@ namespace Volo.CmsKit.Blogs
{ {
} }
public async Task<BlogPost> GetByUrlSlugAsync(Guid blogId, string urlSlug, CancellationToken cancellationToken = default) public async Task<BlogPost> GetBySlugAsync(Guid blogId, string slug, CancellationToken cancellationToken = default)
{ {
var dbSet = await GetDbSetAsync(); var dbSet = await GetDbSetAsync();
return await dbSet return await dbSet
.Include(i=> i.Creator) .Include(i=> i.Creator)
.Where(x => .Where(x =>
x.BlogId == blogId && x.UrlSlug.ToLower() == urlSlug) x.BlogId == blogId && x.Slug.ToLower() == slug)
.FirstOrDefaultAsync(cancellationToken: cancellationToken) .FirstOrDefaultAsync(cancellationToken: cancellationToken)
?? throw new EntityNotFoundException(typeof(BlogPost)); ?? throw new EntityNotFoundException(typeof(BlogPost));
} }
@ -60,7 +60,7 @@ namespace Volo.CmsKit.Blogs
{ {
var dbSet = await GetDbSetAsync(); var dbSet = await GetDbSetAsync();
return await dbSet.AnyAsync(x => x.BlogId == blogId && x.UrlSlug.ToLower() == slug, cancellationToken); return await dbSet.AnyAsync(x => x.BlogId == blogId && x.Slug.ToLower() == slug, cancellationToken);
} }
} }
} }

4
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogRepository.cs

@ -19,9 +19,9 @@ namespace Volo.CmsKit.Blogs
return await (await GetQueryableAsync()).AnyAsync(x => x.Id == blogId); return await (await GetQueryableAsync()).AnyAsync(x => x.Id == blogId);
} }
public Task<Blog> GetByUrlSlugAsync(string urlSlug) public Task<Blog> GetBySlugAsync(string slug)
{ {
return GetAsync(x => x.UrlSlug == urlSlug); return GetAsync(x => x.Slug == slug);
} }
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs

@ -199,7 +199,7 @@ namespace Volo.CmsKit.EntityFrameworkCore
b.Property(p => p.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength); b.Property(p => p.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength);
b.Property(p => p.UrlSlug).IsRequired().HasMaxLength(BlogConsts.MaxUrlSlugLength); b.Property(p => p.Slug).IsRequired().HasMaxLength(BlogConsts.MaxSlugLength);
}); });
builder.Entity<BlogPost>(b => builder.Entity<BlogPost>(b =>
@ -210,11 +210,11 @@ namespace Volo.CmsKit.EntityFrameworkCore
b.Property(p => p.Title).IsRequired().HasMaxLength(BlogPostConsts.MaxTitleLength); b.Property(p => p.Title).IsRequired().HasMaxLength(BlogPostConsts.MaxTitleLength);
b.Property(p => p.UrlSlug).IsRequired().HasMaxLength(BlogPostConsts.MaxUrlSlugLength); b.Property(p => p.Slug).IsRequired().HasMaxLength(BlogPostConsts.MaxSlugLength);
b.Property(p => p.ShortDescription).HasMaxLength(BlogPostConsts.MaxShortDescriptionLength); b.Property(p => p.ShortDescription).HasMaxLength(BlogPostConsts.MaxShortDescriptionLength);
b.HasIndex(x => new { x.UrlSlug, x.BlogId }); b.HasIndex(x => new { x.Slug, x.BlogId });
}); });
} }
else else

6
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs

@ -20,11 +20,11 @@ namespace Volo.CmsKit.MongoDB.Blogs
{ {
} }
public Task<BlogPost> GetByUrlSlugAsync(Guid blogId, string urlSlug, CancellationToken cancellationToken = default) public Task<BlogPost> GetBySlugAsync(Guid blogId, string slug, CancellationToken cancellationToken = default)
{ {
return GetAsync(x => return GetAsync(x =>
x.BlogId == blogId && x.BlogId == blogId &&
x.UrlSlug.ToLower() == urlSlug, x.Slug.ToLower() == slug,
includeDetails: true, includeDetails: true,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -61,7 +61,7 @@ namespace Volo.CmsKit.MongoDB.Blogs
{ {
var queryable = await GetMongoQueryableAsync(); var queryable = await GetMongoQueryableAsync();
return await queryable.AnyAsync(x => x.BlogId == blogId && x.UrlSlug.ToLower() == slug, cancellationToken); return await queryable.AnyAsync(x => x.BlogId == blogId && x.Slug.ToLower() == slug, cancellationToken);
} }
} }
} }

4
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs

@ -20,9 +20,9 @@ namespace Volo.CmsKit.MongoDB.Blogs
x => x.Id == blogId); x => x.Id == blogId);
} }
public Task<Blog> GetByUrlSlugAsync(string urlSlug) public Task<Blog> GetBySlugAsync(string slug)
{ {
return GetAsync(x => x.UrlSlug == urlSlug); return GetAsync(x => x.Slug == slug);
} }
} }
} }

2
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostPublicDto.cs

@ -10,7 +10,7 @@ namespace Volo.CmsKit.Public.Blogs
public string Title { get; set; } public string Title { get; set; }
public string UrlSlug { get; set; } public string Slug { get; set; }
public string ShortDescription { get; set; } public string ShortDescription { get; set; }
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs

@ -8,9 +8,9 @@ namespace Volo.CmsKit.Public.Blogs
{ {
public interface IBlogPostPublicAppService public interface IBlogPostPublicAppService
{ {
Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogUrlSlug, PagedAndSortedResultRequestDto input); Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input);
Task<BlogPostPublicDto> GetAsync(string blogUrlSlug, string blogPostUrlSlug); Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug);
Task<RemoteStreamContent> GetCoverImageAsync(Guid id); Task<RemoteStreamContent> GetCoverImageAsync(Guid id);
} }

10
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs

@ -26,18 +26,18 @@ namespace Volo.CmsKit.Public.Blogs
BlobContainer = blobContainer; BlobContainer = blobContainer;
} }
public async Task<BlogPostPublicDto> GetAsync(string blogUrlSlug, string blogPostUrlSlug) public async Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug)
{ {
var blog = await BlogRepository.GetByUrlSlugAsync(blogUrlSlug); var blog = await BlogRepository.GetBySlugAsync(blogSlug);
var blogPost = await BlogPostRepository.GetByUrlSlugAsync(blog.Id, blogPostUrlSlug); var blogPost = await BlogPostRepository.GetBySlugAsync(blog.Id, blogPostSlug);
return ObjectMapper.Map<BlogPost, BlogPostPublicDto>(blogPost); return ObjectMapper.Map<BlogPost, BlogPostPublicDto>(blogPost);
} }
public async Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogUrlSlug, PagedAndSortedResultRequestDto input) public async Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input)
{ {
var blog = await BlogRepository.GetByUrlSlugAsync(blogUrlSlug); var blog = await BlogRepository.GetBySlugAsync(blogSlug);
var blogPosts = await BlogPostRepository.GetPagedListAsync(blog.Id, input.SkipCount, input.MaxResultCount, input.Sorting); var blogPosts = await BlogPostRepository.GetPagedListAsync(blog.Id, input.SkipCount, input.MaxResultCount, input.Sorting);

8
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs

@ -27,9 +27,9 @@ namespace Volo.CmsKit.Public.Blogs
} }
[HttpGet] [HttpGet]
public Task<BlogPostPublicDto> GetAsync(string blogUrlSlug, string blogPostUrlSlug) public Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug)
{ {
return BlogPostPublicAppService.GetAsync(blogUrlSlug, blogPostUrlSlug); return BlogPostPublicAppService.GetAsync(blogSlug, blogPostSlug);
} }
[HttpGet] [HttpGet]
@ -45,9 +45,9 @@ namespace Volo.CmsKit.Public.Blogs
} }
[HttpGet] [HttpGet]
public Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogUrlSlug, PagedAndSortedResultRequestDto input) public Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input)
{ {
return BlogPostPublicAppService.GetListAsync(blogUrlSlug, input); return BlogPostPublicAppService.GetListAsync(blogSlug, input);
} }
} }
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs

@ -61,10 +61,6 @@ namespace Volo.CmsKit.Public.Web
{ {
Configure<RazorPagesOptions>(options => Configure<RazorPagesOptions>(options =>
{ {
if (GlobalFeatureManager.Instance.IsEnabled<PagesFeature>())
{
options.Conventions.AddPageRoute("/CmsKit/Pages/Index", @"{*pageUrl:minlength(1)}");
}
}); });
} }
} }

34
modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/PageController.cs

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.CmsKit.Public.Pages;
namespace Volo.CmsKit.Public.Web.Controllers
{
public class PageController : CmsKitPublicControllerBase
{
protected IPageAppService PageAppService { get; }
public PageController(IPageAppService pageAppService)
{
PageAppService = pageAppService;
}
[HttpGet("/{*url}", Order = int.MaxValue)]
public async Task<IActionResult> IndexAsync(string url)
{
var page = await PageAppService.FindByUrlAsync(url);
if (page == null)
{
return NotFound();
}
return View("~/Views/Page/Index.cshtml", page);
}
}
}

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/BlogPost.cshtml

@ -1,4 +1,4 @@
@page "/blogs/{blogUrlSlug}/{blogPostUrlSlug}" @page "/blogs/{blogSlug}/{blogPostSlug}"
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPost @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPost
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment
@ -20,8 +20,8 @@
@await Component.InvokeAsync(typeof(DefaultBlogPostViewComponent), new @await Component.InvokeAsync(typeof(DefaultBlogPostViewComponent), new
{ {
Model.BlogUrlSlug, Model.BlogSlug,
Model.BlogPostUrlSlug Model.BlogPostSlug
}) })
@if (GlobalFeatureManager.Instance.IsEnabled<TagsFeature>()) @if (GlobalFeatureManager.Instance.IsEnabled<TagsFeature>())

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/BlogPost.cshtml.cs

@ -11,10 +11,10 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs
public class BlogPostModel : CmsKitPublicPageModelBase public class BlogPostModel : CmsKitPublicPageModelBase
{ {
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string BlogUrlSlug { get; set; } public string BlogSlug { get; set; }
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string BlogPostUrlSlug { get; set; } public string BlogPostSlug { get; set; }
public BlogPostPublicDto BlogPost { get; private set; } public BlogPostPublicDto BlogPost { get; private set; }
@ -27,7 +27,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs
public async Task OnGetAsync() public async Task OnGetAsync()
{ {
BlogPost = await BlogPostPublicAppService.GetAsync(BlogUrlSlug, BlogPostUrlSlug); BlogPost = await BlogPostPublicAppService.GetAsync(BlogSlug, BlogPostSlug);
} }
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/Index.cshtml

@ -1,4 +1,4 @@
@page "/blogs/{blogUrlSlug}" @page "/blogs/{blogSlug}"
@using Volo.CmsKit.Public.Web.Pages @using Volo.CmsKit.Public.Web.Pages
@ -20,12 +20,12 @@
</abp-card-subtitle> </abp-card-subtitle>
</abp-card-header> </abp-card-header>
<abp-card-body> <abp-card-body>
<img src="/api/cms-kit-public/blog-posts/@blog.Id/cover-image" class="card-img-top" onerror="this.src='https://dummyimage.com/300x200/a3a3a3/fff.png'"/> <img src="/api/cms-kit-public/blog-posts/@blog.Id/cover-image" class="card-img-top" style="max-width:200px;max-height:200px;" onerror="this.src='https://dummyimage.com/300x200/a3a3a3/fff.png'" />
<abp-card-text> <abp-card-text>
@blog.ShortDescription @blog.ShortDescription
</abp-card-text> </abp-card-text>
<abp-card-text> <abp-card-text>
<a href="/blogs/@Model.BlogUrlSlug/@blog.UrlSlug"> <a href="/blogs/@Model.BlogSlug/@blog.Slug">
<abp-button text="@L["Read"]" <abp-button text="@L["Read"]"
button-type="Outline_Primary" /> button-type="Outline_Primary" />
</a> </a>

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Blogs/Index.cshtml.cs

@ -15,7 +15,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs
public const int PageSize = 12; public const int PageSize = 12;
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string BlogUrlSlug { get; set; } public string BlogSlug { get; set; }
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public int CurrentPage { get; set; } = 1; public int CurrentPage { get; set; } = 1;
@ -34,7 +34,7 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs
public async Task OnGetAsync() public async Task OnGetAsync()
{ {
Blogs = await BlogPostPublicAppService.GetListAsync( Blogs = await BlogPostPublicAppService.GetListAsync(
BlogUrlSlug, BlogSlug,
new PagedAndSortedResultRequestDto new PagedAndSortedResultRequestDto
{ {
SkipCount = PageSize * (CurrentPage - 1), SkipCount = PageSize * (CurrentPage - 1),

13
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml

@ -1,13 +0,0 @@
@page "{*pageUrl}"
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.CmsKit.Localization
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Pages.IndexModel
@inject IHtmlLocalizer<CmsKitResource> L
@await Component.InvokeAsync(typeof(DefaultPageViewComponent),
new
{
pageId = Model.Page.Id,
title = Model.Page.Title
})

34
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Pages/Index.cshtml.cs

@ -1,34 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.CmsKit.Public.Pages;
using Volo.CmsKit.Web.Pages;
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Pages
{
public class IndexModel : CommonPageModel
{
[BindProperty(SupportsGet = true)]
public string PageUrl { get; set; }
protected readonly IPageAppService PageAppService;
public PageDto Page;
public IndexModel(IPageAppService pageAppService)
{
PageAppService = pageAppService;
}
public async Task<IActionResult> OnGetAsync()
{
Page = await PageAppService.FindByUrlAsync(PageUrl);
if (Page == null)
{
return NotFound();
}
return Page();
}
}
}

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml

@ -10,11 +10,11 @@
<abp-card> <abp-card>
<abp-card-header> <abp-card-header>
<abp-card-title>@Model.Title</abp-card-title> <abp-card-title>@Model.Title</abp-card-title>
<abp-card-subtitle>@@@Model.Creator?.UserName</abp-card-subtitle> <abp-card-subtitle>@Model.Creator?.UserName</abp-card-subtitle>
</abp-card-header> </abp-card-header>
<abp-card-body> <abp-card-body>
<img src="/api/cms-kit-public/blog-posts/@Model.Id/cover-image" class="card-img-top" onerror="this.src='https://dummyimage.com/300x200/a3a3a3/fff.png'" /> @*<img src="/api/cms-kit-public/blog-posts/@Model.Id/cover-image" class="card-img-top" style="max-width:200px;max-height:200px;" onerror="this.src='https://dummyimage.com/300x200/a3a3a3/fff.png'" />*@
@await Component.InvokeAsync(typeof(ContentViewComponent), new @await Component.InvokeAsync(typeof(ContentViewComponent), new
{ {
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Blogs/BlogPost/DefaultBlogPostViewComponent.cs

@ -19,9 +19,9 @@ namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPost
BlogPostPublicAppService = blogPostPublicAppService; BlogPostPublicAppService = blogPostPublicAppService;
} }
public virtual async Task<IViewComponentResult> InvokeAsync(string blogUrlSlug, string blogPostUrlSlug) public virtual async Task<IViewComponentResult> InvokeAsync(string blogSlug, string blogPostSlug)
{ {
var blogPost = await BlogPostPublicAppService.GetAsync(blogUrlSlug, blogPostUrlSlug); var blogPost = await BlogPostPublicAppService.GetAsync(blogSlug, blogPostSlug);
return View("~/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml", blogPost); return View("~/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml", blogPost);
} }

9
modules/cms-kit/src/Volo.CmsKit.Public.Web/Views/Page/Index.cshtml

@ -0,0 +1,9 @@
@model Volo.CmsKit.Public.Pages.PageDto
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Pages
@await Component.InvokeAsync(typeof(DefaultPageViewComponent), new
{
pageId = Model.Id,
title = Model.Title
})

42
modules/cms-kit/test/Volo.CmsKit.Application.Tests/Blogs/BlogPostAdminAppService_Tests.cs

@ -28,14 +28,14 @@ namespace Volo.CmsKit.Blogs
public async Task CreateAsync_ShouldWorkProperly_WithCorrectData() public async Task CreateAsync_ShouldWorkProperly_WithCorrectData()
{ {
var title = "My awesome new Post"; var title = "My awesome new Post";
var urlSlug = "my-awesome-new-post"; var slug = "my-awesome-new-post";
var shortDescription = "This blog is all about awesomeness 🤗!"; var shortDescription = "This blog is all about awesomeness 🤗!";
var created = await blogPostAdminAppService.CreateAsync(new CreateUpdateBlogPostDto var created = await blogPostAdminAppService.CreateAsync(new CreateUpdateBlogPostDto
{ {
BlogId = cmsKitTestData.Blog_Id, BlogId = cmsKitTestData.Blog_Id,
Title = title, Title = title,
UrlSlug = urlSlug, Slug = slug,
ShortDescription = shortDescription ShortDescription = shortDescription
}); });
@ -44,7 +44,7 @@ namespace Volo.CmsKit.Blogs
var blogPost = await blogPostRepository.GetAsync(created.Id); var blogPost = await blogPostRepository.GetAsync(created.Id);
blogPost.Title.ShouldBe(title); blogPost.Title.ShouldBe(title);
blogPost.UrlSlug.ShouldBe(urlSlug); blogPost.Slug.ShouldBe(slug);
blogPost.ShortDescription.ShouldBe(shortDescription); blogPost.ShortDescription.ShouldBe(shortDescription);
} }
@ -52,7 +52,7 @@ namespace Volo.CmsKit.Blogs
public async Task CreateAsync_ShouldThrowException_WithNonExistingBlogId() public async Task CreateAsync_ShouldThrowException_WithNonExistingBlogId()
{ {
var title = "Another My Awesome New Post"; var title = "Another My Awesome New Post";
var urlSlug = "another-my-awesome-new-post"; var slug = "another-my-awesome-new-post";
var shortDescription = "This blog is all about awesomeness 🤗!"; var shortDescription = "This blog is all about awesomeness 🤗!";
var dto = new CreateUpdateBlogPostDto var dto = new CreateUpdateBlogPostDto
@ -60,7 +60,7 @@ namespace Volo.CmsKit.Blogs
// Non-existing Id // Non-existing Id
BlogId = Guid.NewGuid(), BlogId = Guid.NewGuid(),
Title = title, Title = title,
UrlSlug = urlSlug, Slug = slug,
ShortDescription = shortDescription ShortDescription = shortDescription
}; };
@ -76,7 +76,7 @@ namespace Volo.CmsKit.Blogs
var blogPost = await blogPostAdminAppService.GetAsync(cmsKitTestData.BlogPost_1_Id); var blogPost = await blogPostAdminAppService.GetAsync(cmsKitTestData.BlogPost_1_Id);
blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title); blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title);
blogPost.UrlSlug.ShouldBe(cmsKitTestData.BlogPost_1_UrlSlug); blogPost.Slug.ShouldBe(cmsKitTestData.BlogPost_1_Slug);
} }
[Fact] [Fact]
@ -91,30 +91,30 @@ namespace Volo.CmsKit.Blogs
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldWorkProperly_WithExistingUrlSlug() public async Task GetBySlugAsync_ShouldWorkProperly_WithExistingSlug()
{ {
var blogPost = await blogPostAdminAppService.GetByUrlSlugAsync(cmsKitTestData.BlogUrlSlug, cmsKitTestData.BlogPost_1_UrlSlug); var blogPost = await blogPostAdminAppService.GetBySlugAsync(cmsKitTestData.BlogSlug, cmsKitTestData.BlogPost_1_Slug);
blogPost.Id.ShouldBe(cmsKitTestData.BlogPost_1_Id); blogPost.Id.ShouldBe(cmsKitTestData.BlogPost_1_Id);
blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title); blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title);
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldThrowException_WithNonExistingBlogPostUrlSlug() public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogPostSlug()
{ {
var nonExistingUrlSlug = "any-other-url"; var nonExistingSlug = "any-other-url";
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => var exception = await Should.ThrowAsync<EntityNotFoundException>(async () =>
await blogPostAdminAppService.GetByUrlSlugAsync(cmsKitTestData.BlogUrlSlug, nonExistingUrlSlug)); await blogPostAdminAppService.GetBySlugAsync(cmsKitTestData.BlogSlug, nonExistingSlug));
exception.EntityType.ShouldBe(typeof(BlogPost)); exception.EntityType.ShouldBe(typeof(BlogPost));
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldThrowException_WithNonExistingBlogUrlSlug() public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogSlug()
{ {
var nonExistingUrlSlug = "any-other-url"; var nonExistingSlug = "any-other-url";
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => var exception = await Should.ThrowAsync<EntityNotFoundException>(async () =>
await blogPostAdminAppService.GetByUrlSlugAsync(nonExistingUrlSlug, cmsKitTestData.Page_1_Url)); await blogPostAdminAppService.GetBySlugAsync(nonExistingSlug, cmsKitTestData.Page_1_Url));
exception.EntityType.ShouldBe(typeof(Blog)); exception.EntityType.ShouldBe(typeof(Blog));
} }
@ -135,21 +135,21 @@ namespace Volo.CmsKit.Blogs
{ {
var shortDescription = "Another short description"; var shortDescription = "Another short description";
var title = "[Solved] Another Blog Post"; var title = "[Solved] Another Blog Post";
var urlSlug = "another-short-blog-post"; var slug = "another-short-blog-post";
await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, new CreateUpdateBlogPostDto await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, new CreateUpdateBlogPostDto
{ {
BlogId = cmsKitTestData.Blog_Id, BlogId = cmsKitTestData.Blog_Id,
ShortDescription = shortDescription, ShortDescription = shortDescription,
Title = title, Title = title,
UrlSlug = urlSlug, Slug = slug,
}); });
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_2_Id); var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_2_Id);
blogPost.Title.ShouldBe(title); blogPost.Title.ShouldBe(title);
blogPost.ShortDescription.ShouldBe(shortDescription); blogPost.ShortDescription.ShouldBe(shortDescription);
blogPost.UrlSlug.ShouldBe(urlSlug); blogPost.Slug.ShouldBe(slug);
} }
[Fact] [Fact]
@ -161,7 +161,7 @@ namespace Volo.CmsKit.Blogs
{ {
BlogId = nonExistingId, BlogId = nonExistingId,
Title = cmsKitTestData.Page_2_Title, Title = cmsKitTestData.Page_2_Title,
UrlSlug = cmsKitTestData.BlogPost_2_UrlSlug Slug = cmsKitTestData.BlogPost_2_Slug
}; };
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => var exception = await Should.ThrowAsync<EntityNotFoundException>(async () =>
@ -178,13 +178,13 @@ namespace Volo.CmsKit.Blogs
{ {
BlogId = cmsKitTestData.Blog_Id, BlogId = cmsKitTestData.Blog_Id,
Title = "Some new title", Title = "Some new title",
UrlSlug = cmsKitTestData.BlogPost_1_UrlSlug Slug = cmsKitTestData.BlogPost_1_Slug
}; };
var exception = await Should.ThrowAsync<BlogPostUrlSlugAlreadyExistException>(async () => var exception = await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () =>
await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, dto)); await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, dto));
exception.UrlSlug.ShouldBe(cmsKitTestData.BlogPost_1_UrlSlug); exception.Slug.ShouldBe(cmsKitTestData.BlogPost_1_Slug);
} }
[Fact] [Fact]

28
modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Blogs/BlogPostManager_Tests.cs

@ -29,24 +29,24 @@ namespace Volo.CmsKit.Blogs
public async Task CreateAsync_ShouldWorkProperly_WithCorrectData() public async Task CreateAsync_ShouldWorkProperly_WithCorrectData()
{ {
var title = "New blog post"; var title = "New blog post";
var urlSlug = "new-blog-post"; var slug = "new-blog-post";
var created = await blogPostManager.CreateAsync( var created = await blogPostManager.CreateAsync(
new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, title, urlSlug)); new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, title, slug));
created.Id.ShouldNotBe(Guid.Empty); created.Id.ShouldNotBe(Guid.Empty);
var blogPost = await blogPostRepository.GetAsync(created.Id); var blogPost = await blogPostRepository.GetAsync(created.Id);
blogPost.Title.ShouldBe(title); blogPost.Title.ShouldBe(title);
blogPost.UrlSlug.ShouldBe(urlSlug); blogPost.Slug.ShouldBe(slug);
} }
[Fact] [Fact]
public async Task CreateAsync_ShouldThrowException_WhenUrlSlugAlreadyExists() public async Task CreateAsync_ShouldThrowException_WhenSlugAlreadyExists()
{ {
var blogPost = new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, "Any New Title", cmsKitTestData.BlogPost_1_UrlSlug); var blogPost = new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, "Any New Title", cmsKitTestData.BlogPost_1_Slug);
await Should.ThrowAsync<BlogPostUrlSlugAlreadyExistException>(async () => await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () =>
await blogPostManager.CreateAsync(blogPost)); await blogPostManager.CreateAsync(blogPost));
} }
@ -66,27 +66,27 @@ namespace Volo.CmsKit.Blogs
} }
[Fact] [Fact]
public async Task SetUrlSlugAsync_ShouldWorkProperly_WithNonExistingSlug() public async Task SetSlugAsync_ShouldWorkProperly_WithNonExistingSlug()
{ {
var newUrlSlug = "yet-another-post"; var newSlug = "yet-another-post";
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id);
await blogPostManager.SetSlugUrlAsync(blogPost, newUrlSlug); await blogPostManager.SetSlugUrlAsync(blogPost, newSlug);
blogPost.UrlSlug.ShouldBe(newUrlSlug); blogPost.Slug.ShouldBe(newSlug);
} }
[Fact] [Fact]
public async Task SetUrlSlugAsync_ShouldThrowException_WithExistingSlug() public async Task SetSlugAsync_ShouldThrowException_WithExistingSlug()
{ {
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id);
var exception = await Should.ThrowAsync<BlogPostUrlSlugAlreadyExistException>(async () => var exception = await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () =>
await blogPostManager.SetSlugUrlAsync(blogPost, cmsKitTestData.BlogPost_2_UrlSlug)); await blogPostManager.SetSlugUrlAsync(blogPost, cmsKitTestData.BlogPost_2_Slug));
exception.BlogId.ShouldBe(blogPost.BlogId); exception.BlogId.ShouldBe(blogPost.BlogId);
exception.UrlSlug.ShouldBe(cmsKitTestData.BlogPost_2_UrlSlug); exception.Slug.ShouldBe(cmsKitTestData.BlogPost_2_Slug);
} }
} }
} }

45
modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Blogs/UrlSlugExtensions_Tests.cs → modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Blogs/SlugExtensions_Tests.cs

@ -6,131 +6,132 @@ using System.Threading.Tasks;
using Xunit; using Xunit;
using Volo.CmsKit.Blogs.Extensions; using Volo.CmsKit.Blogs.Extensions;
using Shouldly; using Shouldly;
using Unidecode.NET;
namespace Volo.CmsKit.Blogs namespace Volo.CmsKit.Blogs
{ {
public class UrlSlugExtensions_Tests public class SlugExtensions_Tests
{ {
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly() public void NormalizeSlug_ShouldWorkProperly()
{ {
// Arrange // Arrange
var name = "My awesome name"; var name = "My awesome name";
var expected = "my-awesome-name"; var expected = "my-awesome-name";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithDot() public void NormalizeSlug_ShouldWorkProperly_WithDot()
{ {
// Arrange // Arrange
var name = "My Perfect Title v.2"; var name = "My Perfect Title v.2";
var expected = "my-perfect-title-v2"; var expected = "my-perfect-title-v.2";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithQuestionMark() public void NormalizeSlug_ShouldWorkProperly_WithQuestionMark()
{ {
// Arrange // Arrange
var name = "Are you gonna die?"; var name = "Are you gonna die?";
var expected = "are-you-gonna-die"; var expected = "are-you-gonna-die";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithAmpersand() public void NormalizeSlug_ShouldWorkProperly_WithAmpersand()
{ {
// Arrange // Arrange
var name = "We & Machines Challenge"; var name = "We & Machines Challenge";
var expected = "we-machines-challenge"; var expected = "we-machines-challenge";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithDoubleDash() public void NormalizeSlug_ShouldWorkProperly_WithDoubleDash()
{ {
// Arrange // Arrange
var name = "Go and Code --part 2"; var name = "Go and Code --part 2";
var expected = "go-and-code-part-2"; var expected = "go-and-code-part-2";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithCyrillicChars() public void NormalizeSlug_ShouldWorkProperly_WithCyrillicChars()
{ {
// Arrange // Arrange
var name = "Мое классное название"; var name = "Мое классное название";
var expected = "moe-klassnoe-nazvanie"; var expected = "moe-klassnoe-nazvanie";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithTurkishChars() public void NormalizeSlug_ShouldWorkProperly_WithTurkishChars()
{ {
// Arrange // Arrange
var name = "Özel Türkçe karakterler: ğüşiöç"; var name = "Özel Türkçe karakterler: ğüşiöç";
var expected = "ozel-turkce-karakterler-gusioc"; var expected = "ozel-turkce-karakterler-gusioc";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithChineseChars() public void NormalizeSlug_ShouldWorkProperly_WithChineseChars()
{ {
// Arrange // Arrange
var name = "我的挑战"; var name = "我的真棒头衔";
var expected = "o-e-iao-han"; var expected = "wo-de-zhen-bang-tou-xian";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);
} }
[Fact] [Fact]
public void NormalizeAsUrlSlug_ShouldWorkProperly_WithEmoji() public void NormalizeSlug_ShouldWorkProperly_WithEmoji()
{ {
// Arrange // Arrange
var name = "Let's Rock 👊"; var name = "Let's Rock 👊";
var expected = "lets-rock"; var expected = "lets-rock";
// Act // Act
var actual = name.NormalizeAsUrlSlug(); var actual = name.NormalizeSlug();
// Assert // Assert
actual.ShouldBe(expected); actual.ShouldBe(expected);

22
modules/cms-kit/test/Volo.CmsKit.TestBase/Blogs/BlogPostRepository_Test.cs

@ -23,15 +23,15 @@ namespace Volo.CmsKit.Blogs
} }
[Fact] [Fact]
public async Task SlugExistsAsync_ShouldReturnTrue_WithExistingUrlSlug() public async Task SlugExistsAsync_ShouldReturnTrue_WithExistingSlug()
{ {
var result = await blogPostRepository.SlugExistsAsync(testData.Blog_Id, testData.BlogPost_1_UrlSlug); var result = await blogPostRepository.SlugExistsAsync(testData.Blog_Id, testData.BlogPost_1_Slug);
result.ShouldBeTrue(); result.ShouldBeTrue();
} }
[Fact] [Fact]
public async Task SlugExistsAsync_ShouldReturnFalse_WithNonExistingUrlSlug() public async Task SlugExistsAsync_ShouldReturnFalse_WithNonExistingSlug()
{ {
var nonExistingSlug = "any-other-url-slug"; var nonExistingSlug = "any-other-url-slug";
@ -45,38 +45,38 @@ namespace Volo.CmsKit.Blogs
{ {
var nonExistingBlogId = Guid.NewGuid(); var nonExistingBlogId = Guid.NewGuid();
var result = await blogPostRepository.SlugExistsAsync(nonExistingBlogId, testData.BlogPost_1_UrlSlug); var result = await blogPostRepository.SlugExistsAsync(nonExistingBlogId, testData.BlogPost_1_Slug);
result.ShouldBeFalse(); result.ShouldBeFalse();
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldWorkProperly_WithCorrectParameters() public async Task GetBySlugAsync_ShouldWorkProperly_WithCorrectParameters()
{ {
var blogPost = await blogPostRepository.GetByUrlSlugAsync(testData.Blog_Id, testData.BlogPost_1_UrlSlug); var blogPost = await blogPostRepository.GetBySlugAsync(testData.Blog_Id, testData.BlogPost_1_Slug);
blogPost.ShouldNotBeNull(); blogPost.ShouldNotBeNull();
blogPost.Id.ShouldBe(testData.BlogPost_1_Id); blogPost.Id.ShouldBe(testData.BlogPost_1_Id);
blogPost.UrlSlug.ShouldBe(testData.BlogPost_1_UrlSlug); blogPost.Slug.ShouldBe(testData.BlogPost_1_Slug);
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldThrowException_WithNonExistingBlogPostUrlSlug() public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogPostSlug()
{ {
var nonExistingSlugUrl = "absolutely-non-existing-url"; var nonExistingSlugUrl = "absolutely-non-existing-url";
var exception = await Should.ThrowAsync<EntityNotFoundException>( var exception = await Should.ThrowAsync<EntityNotFoundException>(
async () => await blogPostRepository.GetByUrlSlugAsync(testData.Blog_Id, nonExistingSlugUrl)); async () => await blogPostRepository.GetBySlugAsync(testData.Blog_Id, nonExistingSlugUrl));
exception.ShouldNotBeNull(); exception.ShouldNotBeNull();
exception.EntityType.ShouldBe(typeof(BlogPost)); exception.EntityType.ShouldBe(typeof(BlogPost));
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldThrowException_WithNonExistingBlogId() public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogId()
{ {
var nonExistingBlogId = Guid.NewGuid(); var nonExistingBlogId = Guid.NewGuid();
var exception = await Should.ThrowAsync<EntityNotFoundException>( var exception = await Should.ThrowAsync<EntityNotFoundException>(
async () => await blogPostRepository.GetByUrlSlugAsync(nonExistingBlogId, testData.BlogPost_1_UrlSlug)); async () => await blogPostRepository.GetBySlugAsync(nonExistingBlogId, testData.BlogPost_1_Slug));
exception.ShouldNotBeNull(); exception.ShouldNotBeNull();
exception.EntityType.ShouldBe(typeof(BlogPost)); exception.EntityType.ShouldBe(typeof(BlogPost));

10
modules/cms-kit/test/Volo.CmsKit.TestBase/Blogs/BlogRepository_Test.cs

@ -23,22 +23,22 @@ namespace Volo.CmsKit.Blogs
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldWorkProperly_WithExistingSlug() public async Task GetBySlugAsync_ShouldWorkProperly_WithExistingSlug()
{ {
var blog = await blogRepository.GetByUrlSlugAsync(testData.BlogUrlSlug); var blog = await blogRepository.GetBySlugAsync(testData.BlogSlug);
blog.ShouldNotBeNull(); blog.ShouldNotBeNull();
blog.UrlSlug.ShouldBe(testData.BlogUrlSlug); blog.Slug.ShouldBe(testData.BlogSlug);
blog.Id.ShouldBe(testData.Blog_Id); blog.Id.ShouldBe(testData.Blog_Id);
} }
[Fact] [Fact]
public async Task GetByUrlSlugAsync_ShouldThrowException_WithNonExistingSlug() public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingSlug()
{ {
var nonExistingSlug = "some-blog-slug-that-doesnt-exist"; var nonExistingSlug = "some-blog-slug-that-doesnt-exist";
var exception = await Should.ThrowAsync<EntityNotFoundException>( var exception = await Should.ThrowAsync<EntityNotFoundException>(
async () => await blogRepository.GetByUrlSlugAsync(nonExistingSlug)); async () => await blogRepository.GetBySlugAsync(nonExistingSlug));
exception.ShouldNotBeNull(); exception.ShouldNotBeNull();
exception.EntityType.ShouldBe(typeof(Blog)); exception.EntityType.ShouldBe(typeof(Blog));

6
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs

@ -308,11 +308,11 @@ namespace Volo.CmsKit
private async Task SeedBlogsAsync() private async Task SeedBlogsAsync()
{ {
var blog = await _blogRepository.InsertAsync(new Blog(_cmsKitTestData.Blog_Id, _cmsKitTestData.BlogName, _cmsKitTestData.BlogUrlSlug)); var blog = await _blogRepository.InsertAsync(new Blog(_cmsKitTestData.Blog_Id, _cmsKitTestData.BlogName, _cmsKitTestData.BlogSlug));
await _blogPostRepository.InsertAsync(new BlogPost(_cmsKitTestData.BlogPost_1_Id, blog.Id, _cmsKitTestData.BlogPost_1_Title, _cmsKitTestData.BlogPost_1_UrlSlug, "Short desc 1")); await _blogPostRepository.InsertAsync(new BlogPost(_cmsKitTestData.BlogPost_1_Id, blog.Id, _cmsKitTestData.BlogPost_1_Title, _cmsKitTestData.BlogPost_1_Slug, "Short desc 1"));
await _blogPostRepository.InsertAsync(new BlogPost(_cmsKitTestData.BlogPost_2_Id, blog.Id, _cmsKitTestData.BlogPost_2_Title, _cmsKitTestData.BlogPost_2_UrlSlug, "Short desc 2")); await _blogPostRepository.InsertAsync(new BlogPost(_cmsKitTestData.BlogPost_2_Id, blog.Id, _cmsKitTestData.BlogPost_2_Title, _cmsKitTestData.BlogPost_2_Slug, "Short desc 2"));
} }
} }
} }

6
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs

@ -77,18 +77,18 @@ namespace Volo.CmsKit
public string BlogName => "Cms Blog"; public string BlogName => "Cms Blog";
public string BlogUrlSlug => "cms-blog"; public string BlogSlug => "cms-blog";
public Guid BlogPost_1_Id { get; } = Guid.NewGuid(); public Guid BlogPost_1_Id { get; } = Guid.NewGuid();
public string BlogPost_1_Title => "How to install CmsKit?"; public string BlogPost_1_Title => "How to install CmsKit?";
public string BlogPost_1_UrlSlug => "how-to-install-cms-kit"; public string BlogPost_1_Slug => "how-to-install-cms-kit";
public Guid BlogPost_2_Id { get; } = Guid.NewGuid(); public Guid BlogPost_2_Id { get; } = Guid.NewGuid();
public string BlogPost_2_Title => "How to use CmsKit"; public string BlogPost_2_Title => "How to use CmsKit";
public string BlogPost_2_UrlSlug => "how-to-use-cms-kit"; public string BlogPost_2_Slug => "how-to-use-cms-kit";
} }
} }

Loading…
Cancel
Save