mirror of https://github.com/abpframework/abp.git
committed by
GitHub
103 changed files with 4874 additions and 90 deletions
File diff suppressed because it is too large
@ -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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public class BlogDto : EntityDto<Guid> |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicMaxLength(typeof(BlogConsts), nameof(BlogConsts.MaxNameLength))] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DynamicMaxLength(typeof(BlogConsts), nameof(BlogConsts.MaxSlugLength))] |
||||
|
public string Slug { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public class BlogGetListInput : PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public class BlogLookupDto |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
public string Name { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public class BlogPostDto : EntityDto<Guid> |
||||
|
{ |
||||
|
public Guid BlogId { get; set; } |
||||
|
|
||||
|
public string Title { get; set; } |
||||
|
|
||||
|
public string Slug { get; set; } |
||||
|
public string ShortDescription { get; protected set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public class CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
[Required] |
||||
|
public Guid BlogId { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DynamicMaxLength(typeof(BlogPostConsts), nameof(BlogPostConsts.MaxTitleLength))] |
||||
|
public string Title { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DynamicStringLength( |
||||
|
typeof(BlogPostConsts), |
||||
|
nameof(BlogPostConsts.MaxSlugLength), |
||||
|
nameof(BlogPostConsts.MinSlugLength))] |
||||
|
public string Slug { get; set; } |
||||
|
|
||||
|
[DynamicMaxLength(typeof(BlogPostConsts), nameof(BlogPostConsts.MaxShortDescriptionLength))] |
||||
|
public string ShortDescription { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public interface IBlogAdminAppService : ICrudAppService<BlogDto, Guid, BlogGetListInput> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Content; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
public interface IBlogPostAdminAppService |
||||
|
: ICrudAppService< |
||||
|
BlogPostDto, |
||||
|
Guid, |
||||
|
PagedAndSortedResultRequestDto, |
||||
|
CreateUpdateBlogPostDto> |
||||
|
{ |
||||
|
Task<BlogPostDto> GetBySlugAsync(string blogSlug, string slug); |
||||
|
|
||||
|
Task SetCoverImageAsync(Guid id, RemoteStreamContent streamContent); |
||||
|
|
||||
|
Task<RemoteStreamContent> GetCoverImageAsync(Guid id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Tags |
||||
|
{ |
||||
|
public class EntityTagSetDto |
||||
|
{ |
||||
|
public string EntityId { get; set; } |
||||
|
public string EntityType { get; set; } |
||||
|
public List<string> Tags { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Permissions; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(BlogsFeature))] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Default)] |
||||
|
public class BlogAdminAppService : CrudAppService<Blog, BlogDto, Guid, BlogGetListInput>, IBlogAdminAppService |
||||
|
{ |
||||
|
public BlogAdminAppService(IRepository<Blog, Guid> repository) : base(repository) |
||||
|
{ |
||||
|
GetListPolicyName = CmsKitAdminPermissions.Blogs.Default; |
||||
|
GetPolicyName = CmsKitAdminPermissions.Blogs.Default; |
||||
|
CreatePolicyName = CmsKitAdminPermissions.Blogs.Create; |
||||
|
UpdatePolicyName = CmsKitAdminPermissions.Blogs.Update; |
||||
|
DeletePolicyName = CmsKitAdminPermissions.Blogs.Delete; |
||||
|
} |
||||
|
|
||||
|
protected override async Task<IQueryable<Blog>> CreateFilteredQueryAsync(BlogGetListInput input) |
||||
|
{ |
||||
|
var queryable = await base.CreateFilteredQueryAsync(input); |
||||
|
return queryable |
||||
|
.WhereIf( |
||||
|
!input.Filter.IsNullOrWhiteSpace(), |
||||
|
x => x.Name.ToLower().Contains(input.Filter)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Content; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.Abp.Users; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Permissions; |
||||
|
using Volo.CmsKit.Users; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(BlogsFeature))] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
public class BlogPostAdminAppService |
||||
|
: CrudAppService< |
||||
|
BlogPost, |
||||
|
BlogPostDto, |
||||
|
Guid, |
||||
|
PagedAndSortedResultRequestDto, |
||||
|
CreateUpdateBlogPostDto> |
||||
|
, IBlogPostAdminAppService |
||||
|
{ |
||||
|
protected readonly IBlogPostManager BlogPostManager; |
||||
|
protected readonly IBlogPostRepository BlogPostRepository; |
||||
|
protected readonly IBlogRepository BlogRepository; |
||||
|
protected readonly IBlobContainer<BlogPostCoverImageContainer> BlobContainer; |
||||
|
protected readonly ICmsUserLookupService UserLookupService; |
||||
|
|
||||
|
public BlogPostAdminAppService( |
||||
|
IRepository<BlogPost, Guid> repository, |
||||
|
IBlogPostManager blogPostManager, |
||||
|
IBlogPostRepository blogPostRepository, |
||||
|
IBlogRepository blogRepository, |
||||
|
IBlobContainer<BlogPostCoverImageContainer> blobContainer, |
||||
|
ICmsUserLookupService userLookupService) : base(repository) |
||||
|
{ |
||||
|
BlogPostManager = blogPostManager; |
||||
|
BlogPostRepository = blogPostRepository; |
||||
|
BlogRepository = blogRepository; |
||||
|
BlobContainer = blobContainer; |
||||
|
UserLookupService = userLookupService; |
||||
|
|
||||
|
GetListPolicyName = CmsKitAdminPermissions.BlogPosts.Default; |
||||
|
GetPolicyName = CmsKitAdminPermissions.BlogPosts.Default; |
||||
|
CreatePolicyName = CmsKitAdminPermissions.BlogPosts.Create; |
||||
|
UpdatePolicyName = CmsKitAdminPermissions.BlogPosts.Update; |
||||
|
DeletePolicyName = CmsKitAdminPermissions.BlogPosts.Delete; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BlogPostDto> GetBySlugAsync(string blogSlug, string blogPostSlug) |
||||
|
{ |
||||
|
var blog = await BlogRepository.GetBySlugAsync(blogSlug); |
||||
|
|
||||
|
var blogPost = await BlogPostRepository.GetBySlugAsync(blog.Id, blogPostSlug); |
||||
|
|
||||
|
return MapToGetOutputDto(blogPost); |
||||
|
} |
||||
|
|
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Create)] |
||||
|
public override async Task<BlogPostDto> CreateAsync(CreateUpdateBlogPostDto input) |
||||
|
{ |
||||
|
_ = await UserLookupService.GetByIdAsync(CurrentUser.GetId()); |
||||
|
|
||||
|
var entity = await BlogPostManager |
||||
|
.CreateAsync( |
||||
|
new BlogPost( |
||||
|
GuidGenerator.Create(), |
||||
|
input.BlogId, |
||||
|
input.Title, |
||||
|
input.Slug, |
||||
|
input.ShortDescription)); |
||||
|
|
||||
|
return MapToGetOutputDto(entity); |
||||
|
} |
||||
|
|
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Update)] |
||||
|
public override async Task<BlogPostDto> UpdateAsync(Guid id, CreateUpdateBlogPostDto input) |
||||
|
{ |
||||
|
var blogPost = await BlogPostRepository.GetAsync(id); |
||||
|
|
||||
|
blogPost.SetTitle(input.Title); |
||||
|
|
||||
|
if (blogPost.Slug != input.Slug) |
||||
|
{ |
||||
|
await BlogPostManager.SetSlugUrlAsync(blogPost, input.Slug); |
||||
|
} |
||||
|
|
||||
|
MapToEntity(input, blogPost); |
||||
|
|
||||
|
await BlogPostManager.UpdateAsync(blogPost); |
||||
|
|
||||
|
return MapToGetOutputDto(blogPost); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SetCoverImageAsync(Guid id, RemoteStreamContent streamContent) |
||||
|
{ |
||||
|
await Repository.GetAsync(id); |
||||
|
|
||||
|
using (var stream = streamContent.GetStream()) |
||||
|
{ |
||||
|
await BlobContainer.SaveAsync(id.ToString(), stream, overrideExisting: true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<RemoteStreamContent> GetCoverImageAsync(Guid id) |
||||
|
{ |
||||
|
var stream = await BlobContainer.GetAsync(id.ToString()); |
||||
|
|
||||
|
return new RemoteStreamContent(stream); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Permissions; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(BlogsFeature))] |
||||
|
[RemoteService(Name = CmsKitCommonRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area("cms-kit")] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Default)] |
||||
|
[Route("api/cms-kit-admin/blogs/blogs")] |
||||
|
public class BlogAdminController : CmsKitAdminController, IBlogAdminAppService |
||||
|
{ |
||||
|
protected IBlogAdminAppService BlogAdminAppService { get; } |
||||
|
|
||||
|
public BlogAdminController(IBlogAdminAppService blogAdminAppService) |
||||
|
{ |
||||
|
BlogAdminAppService = blogAdminAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Create)] |
||||
|
public Task<BlogDto> CreateAsync(BlogDto input) |
||||
|
{ |
||||
|
return BlogAdminAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Delete)] |
||||
|
public Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
return BlogAdminAppService.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Default)] |
||||
|
public Task<BlogDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return BlogAdminAppService.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Default)] |
||||
|
public Task<PagedResultDto<BlogDto>> GetListAsync([FromQuery] BlogGetListInput input) |
||||
|
{ |
||||
|
return BlogAdminAppService.GetListAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(CmsKitAdminPermissions.Blogs.Update)] |
||||
|
public Task<BlogDto> UpdateAsync(Guid id, BlogDto input) |
||||
|
{ |
||||
|
return BlogAdminAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Content; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Permissions; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Blogs |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(BlogsFeature))] |
||||
|
[RemoteService(Name = CmsKitCommonRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area("cms-kit")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
[Route("api/cms-kit-admin/blogs/blog-posts")] |
||||
|
public class BlogPostAdminController : CmsKitAdminController, IBlogPostAdminAppService |
||||
|
{ |
||||
|
protected readonly IBlogPostAdminAppService BlogPostAdminAppService; |
||||
|
|
||||
|
public BlogPostAdminController(IBlogPostAdminAppService blogPostAdminAppService) |
||||
|
{ |
||||
|
BlogPostAdminAppService = blogPostAdminAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Create)] |
||||
|
public virtual Task<BlogPostDto> CreateAsync(CreateUpdateBlogPostDto input) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Delete)] |
||||
|
public virtual Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id:Guid}")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
public virtual Task<BlogPostDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{blogSlug}/{blogPostSlug}")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
public virtual Task<BlogPostDto> GetBySlugAsync(string blogSlug, string blogPostSlug) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.GetBySlugAsync(blogSlug, blogPostSlug); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}/cover-image")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
public virtual Task<RemoteStreamContent> GetCoverImageAsync(Guid id) |
||||
|
{ |
||||
|
Response.Headers.Add("Content-Disposition", $"inline;filename=\"{id}\""); |
||||
|
Response.Headers.Add("Accept-Ranges", "bytes"); |
||||
|
Response.Headers.Add("Cache-Control", "max-age=120"); |
||||
|
Response.ContentType = "image"; |
||||
|
|
||||
|
return BlogPostAdminAppService.GetCoverImageAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Default)] |
||||
|
public virtual Task<PagedResultDto<BlogPostDto>> GetListAsync(PagedAndSortedResultRequestDto input) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.GetListAsync(input); |
||||
|
} |
||||
|
|
||||
|
[NonAction] |
||||
|
public virtual Task SetCoverImageAsync(Guid id, RemoteStreamContent streamContent) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.SetCoverImageAsync(id, streamContent); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Route("{id}/cover-image")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Update)] |
||||
|
public virtual async Task<IActionResult> UploadCoverImageAsync(Guid id, IFormFile file) |
||||
|
{ |
||||
|
if (file == null) |
||||
|
{ |
||||
|
return BadRequest(); |
||||
|
} |
||||
|
|
||||
|
using (var stream = file.OpenReadStream()) |
||||
|
{ |
||||
|
await BlogPostAdminAppService.SetCoverImageAsync(id, new RemoteStreamContent(stream)); |
||||
|
} |
||||
|
|
||||
|
return CreatedAtAction(nameof(GetCoverImageAsync), new { id }); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(CmsKitAdminPermissions.BlogPosts.Update)] |
||||
|
public virtual Task<BlogPostDto> UpdateAsync(Guid id, CreateUpdateBlogPostDto input) |
||||
|
{ |
||||
|
return BlogPostAdminAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Permissions; |
||||
|
|
||||
|
namespace Volo.CmsKit.Admin.Tags |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(TagsFeature))] |
||||
|
[RemoteService(Name = CmsKitCommonRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area("cms-kit")] |
||||
|
[Route("api/cms-kit-admin/entity-tags")] |
||||
|
public class EntityTagAdminController : CmsKitAdminController, IEntityTagAdminAppService |
||||
|
{ |
||||
|
protected IEntityTagAdminAppService EntityTagAdminAppService { get; } |
||||
|
|
||||
|
public EntityTagAdminController(IEntityTagAdminAppService entityTagAdminAppService) |
||||
|
{ |
||||
|
EntityTagAdminAppService = entityTagAdminAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
public Task AddTagToEntityAsync(EntityTagCreateDto input) |
||||
|
{ |
||||
|
return EntityTagAdminAppService.AddTagToEntityAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
public Task RemoveTagFromEntityAsync(EntityTagRemoveDto input) |
||||
|
{ |
||||
|
return EntityTagAdminAppService.RemoveTagFromEntityAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
public Task SetEntityTagsAsync(EntityTagSetDto input) |
||||
|
{ |
||||
|
return EntityTagAdminAppService.SetEntityTagsAsync(input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace Volo.CmsKit.Users |
||||
|
{ |
||||
|
public class CmsUserDto : EntityDto<Guid> |
||||
|
{ |
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
public virtual string UserName { get; protected set; } |
||||
|
|
||||
|
public virtual string Name { get; set; } |
||||
|
|
||||
|
public virtual string Surname { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogConsts |
||||
|
{ |
||||
|
public static int MaxNameLength { get; set; } = 64; |
||||
|
public static int MaxSlugLength { get; set; } = 64; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public static class BlogPostConsts |
||||
|
{ |
||||
|
public static int MaxTitleLength { get; set; } = 64; |
||||
|
|
||||
|
public static int MaxSlugLength { get; set; } = 256; |
||||
|
|
||||
|
public static int MinSlugLength { get; set; } = 2; |
||||
|
|
||||
|
public static int MaxShortDescriptionLength { get; set; } = 256; |
||||
|
|
||||
|
public const string EntityType = "BlogPost"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
|
||||
|
namespace Volo.CmsKit.GlobalFeatures |
||||
|
{ |
||||
|
[GlobalFeatureName(Name)] |
||||
|
public class BlogsFeature : GlobalFeature |
||||
|
{ |
||||
|
public const string Name = "CmsKit.Blogs"; |
||||
|
|
||||
|
internal BlogsFeature( |
||||
|
[NotNull] GlobalCmsKitFeatures cmsKit |
||||
|
) : base(cmsKit) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.CmsKit.Blogs.Extensions; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class Blog : FullAuditedAggregateRoot<Guid>, IMultiTenant |
||||
|
{ |
||||
|
public Blog( |
||||
|
Guid id, |
||||
|
[NotNull] string name, |
||||
|
[NotNull] string slug, |
||||
|
[CanBeNull] Guid? tenantId = null) : base(id) |
||||
|
{ |
||||
|
SetName(name); |
||||
|
SetSlug(slug); |
||||
|
TenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
public string Name { get; protected set; } |
||||
|
|
||||
|
public string Slug { get; protected set; } |
||||
|
|
||||
|
public Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
public void SetName(string name) |
||||
|
{ |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name), maxLength: BlogConsts.MaxNameLength); |
||||
|
} |
||||
|
|
||||
|
public void SetSlug(string slug) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(slug, nameof(slug), maxLength: BlogConsts.MaxNameLength); |
||||
|
|
||||
|
Slug = slug.NormalizeSlug(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Volo.CmsKit.Blogs.Extensions; |
||||
|
using Volo.CmsKit.Users; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogPost : FullAuditedAggregateRootWithUser<Guid, CmsUser>, IMultiTenant |
||||
|
{ |
||||
|
public Guid BlogId { get; protected set; } |
||||
|
|
||||
|
public string Title { get; protected set; } |
||||
|
|
||||
|
public string Slug { get; protected set; } |
||||
|
|
||||
|
public string ShortDescription { get; protected set; } |
||||
|
|
||||
|
public Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
protected BlogPost() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public BlogPost( |
||||
|
Guid id, |
||||
|
Guid blogId, |
||||
|
[NotNull] string title, |
||||
|
[NotNull] string slug, |
||||
|
[CanBeNull] string shortDescription = null) : base(id) |
||||
|
{ |
||||
|
BlogId = blogId; |
||||
|
SetTitle(title); |
||||
|
SetSlug(slug); |
||||
|
ShortDescription = shortDescription; |
||||
|
} |
||||
|
|
||||
|
public void SetTitle(string title) |
||||
|
{ |
||||
|
Title = Check.NotNullOrWhiteSpace(title, nameof(title), BlogPostConsts.MaxTitleLength); |
||||
|
} |
||||
|
|
||||
|
internal void SetSlug(string slug) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(slug, nameof(slug), BlogPostConsts.MaxSlugLength, BlogPostConsts.MinSlugLength); |
||||
|
|
||||
|
Slug = slug.NormalizeSlug(); |
||||
|
} |
||||
|
|
||||
|
public void SetShortDescription(string shortDescription) |
||||
|
{ |
||||
|
ShortDescription = Check.Length(shortDescription, nameof(shortDescription), BlogPostConsts.MaxShortDescriptionLength); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
[BlobContainerName("blog-post-cover-images")] |
||||
|
public class BlogPostCoverImageContainer |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogPostManager : DomainService, IBlogPostManager |
||||
|
{ |
||||
|
protected readonly IBlogPostRepository blogPostRepository; |
||||
|
protected readonly IBlogRepository blogRepository; |
||||
|
|
||||
|
public BlogPostManager( |
||||
|
IBlogPostRepository blogPostRepository, |
||||
|
IBlogRepository blogRepository) |
||||
|
{ |
||||
|
this.blogPostRepository = blogPostRepository; |
||||
|
this.blogRepository = blogRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<BlogPost> CreateAsync(BlogPost blogPost) |
||||
|
{ |
||||
|
await CheckBlogExistenceAsync(blogPost.BlogId); |
||||
|
|
||||
|
await CheckSlugExistenceAsync(blogPost.BlogId, blogPost.Slug); |
||||
|
|
||||
|
return await blogPostRepository.InsertAsync(blogPost); |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateAsync(BlogPost blogPost) |
||||
|
{ |
||||
|
await CheckBlogExistenceAsync(blogPost.BlogId); |
||||
|
|
||||
|
await blogPostRepository.UpdateAsync(blogPost); |
||||
|
} |
||||
|
|
||||
|
public async Task SetSlugUrlAsync(BlogPost blogPost, string newSlug) |
||||
|
{ |
||||
|
await CheckSlugExistenceAsync(blogPost.BlogId, newSlug); |
||||
|
|
||||
|
blogPost.SetSlug(newSlug); |
||||
|
} |
||||
|
|
||||
|
private async Task CheckSlugExistenceAsync(Guid blogId, string slug) |
||||
|
{ |
||||
|
if (await blogPostRepository.SlugExistsAsync(blogId, slug)) |
||||
|
{ |
||||
|
throw new BlogPostSlugAlreadyExistException(blogId, slug); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task CheckBlogExistenceAsync(Guid blogId) |
||||
|
{ |
||||
|
if (!await blogRepository.ExistsAsync(blogId)) |
||||
|
throw new EntityNotFoundException(typeof(Blog), blogId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public interface IBlogPostManager |
||||
|
{ |
||||
|
Task<BlogPost> CreateAsync(BlogPost blogPost); |
||||
|
|
||||
|
Task UpdateAsync(BlogPost blogPost); |
||||
|
|
||||
|
Task SetSlugUrlAsync(BlogPost blogPost, string newSlug); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public interface IBlogPostRepository : IBasicRepository<BlogPost, Guid> |
||||
|
{ |
||||
|
Task<bool> SlugExistsAsync(Guid blogId, string slug, 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<int> GetCountAsync(Guid blogId, CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public interface IBlogRepository : IBasicRepository<Blog, Guid> |
||||
|
{ |
||||
|
public Task<Blog> GetBySlugAsync(string slug); |
||||
|
Task<bool> ExistsAsync(Guid blogId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.CmsKit.EntityFrameworkCore; |
||||
|
using System.Linq; |
||||
|
using System.Data.Common; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class EfCoreBlogPostRepository : EfCoreRepository<CmsKitDbContext, BlogPost, Guid>, IBlogPostRepository |
||||
|
{ |
||||
|
public EfCoreBlogPostRepository(IDbContextProvider<CmsKitDbContext> dbContextProvider) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<BlogPost> GetBySlugAsync(Guid blogId, string slug, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbSet = await GetDbSetAsync(); |
||||
|
|
||||
|
return await dbSet |
||||
|
.Include(i=> i.Creator) |
||||
|
.Where(x => |
||||
|
x.BlogId == blogId && x.Slug.ToLower() == slug) |
||||
|
.FirstOrDefaultAsync(cancellationToken: cancellationToken) |
||||
|
?? throw new EntityNotFoundException(typeof(BlogPost)); |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync(Guid blogId, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetQueryableAsync()).CountAsync( |
||||
|
x => x.BlogId == blogId, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<BlogPost>> GetPagedListAsync(Guid blogId, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var queryable = (await GetQueryableAsync()) |
||||
|
.Include(i => i.Creator) |
||||
|
.Where(x => x.BlogId == blogId); |
||||
|
|
||||
|
if (!sorting.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
queryable = queryable.OrderBy(sorting); |
||||
|
} |
||||
|
|
||||
|
return await queryable |
||||
|
.Skip(skipCount) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> SlugExistsAsync(Guid blogId, string slug, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbSet = await GetDbSetAsync(); |
||||
|
|
||||
|
return await dbSet.AnyAsync(x => x.BlogId == blogId && x.Slug.ToLower() == slug, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.CmsKit.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class EfCoreBlogRepository : EfCoreRepository<ICmsKitDbContext, Blog, Guid>, IBlogRepository |
||||
|
{ |
||||
|
public EfCoreBlogRepository(IDbContextProvider<ICmsKitDbContext> dbContextProvider) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> ExistsAsync(Guid blogId) |
||||
|
{ |
||||
|
return await (await GetQueryableAsync()).AnyAsync(x => x.Id == blogId); |
||||
|
} |
||||
|
|
||||
|
public Task<Blog> GetBySlugAsync(string slug) |
||||
|
{ |
||||
|
return GetAsync(x => x.Slug == slug); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
using MongoDB.Driver; |
||||
|
using MongoDB.Driver.Linq; |
||||
|
using Nito.AsyncEx; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.MongoDB; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Volo.CmsKit.Users; |
||||
|
|
||||
|
namespace Volo.CmsKit.MongoDB.Blogs |
||||
|
{ |
||||
|
public class MongoBlogPostRepository : MongoDbRepository<CmsKitMongoDbContext, BlogPost, Guid>, IBlogPostRepository |
||||
|
{ |
||||
|
public MongoBlogPostRepository(IMongoDbContextProvider<CmsKitMongoDbContext> dbContextProvider) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Task<BlogPost> GetBySlugAsync(Guid blogId, string slug, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return GetAsync(x => |
||||
|
x.BlogId == blogId && |
||||
|
x.Slug.ToLower() == slug, |
||||
|
includeDetails: true, |
||||
|
cancellationToken: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<int> GetCountAsync(Guid blogId, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await AsyncExecuter.CountAsync( |
||||
|
await WithDetailsAsync(), |
||||
|
x => x.BlogId == blogId, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<BlogPost>> GetPagedListAsync(Guid blogId, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(cancellationToken); |
||||
|
var blogPostQueryable = await WithDetailsAsync(); |
||||
|
|
||||
|
var queryable = blogPostQueryable |
||||
|
.Where(x => x.BlogId == blogId); |
||||
|
|
||||
|
if (!sorting.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
queryable = queryable.OrderBy(sorting); |
||||
|
} |
||||
|
|
||||
|
queryable = queryable |
||||
|
.Skip(skipCount) |
||||
|
.Take(maxResultCount); |
||||
|
|
||||
|
return await AsyncExecuter.ToListAsync(queryable, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> SlugExistsAsync(Guid blogId, string slug, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var queryable = await GetMongoQueryableAsync(); |
||||
|
|
||||
|
return await queryable.AnyAsync(x => x.BlogId == blogId && x.Slug.ToLower() == slug, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using MongoDB.Driver.Core.Operations; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.MongoDB; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.MongoDB.Blogs |
||||
|
{ |
||||
|
public class MongoBlogRepository : MongoDbRepository<ICmsKitMongoDbContext, Blog, Guid>, IBlogRepository |
||||
|
{ |
||||
|
public MongoBlogRepository(IMongoDbContextProvider<ICmsKitMongoDbContext> dbContextProvider) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> ExistsAsync(Guid blogId) |
||||
|
{ |
||||
|
return await AsyncExecuter.AnyAsync( |
||||
|
await GetQueryableAsync(), |
||||
|
x => x.Id == blogId); |
||||
|
} |
||||
|
|
||||
|
public Task<Blog> GetBySlugAsync(string slug) |
||||
|
{ |
||||
|
return GetAsync(x => x.Slug == slug); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.CmsKit.Users; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Blogs |
||||
|
{ |
||||
|
public class BlogPostPublicDto : AuditedEntityWithUserDto<Guid, CmsUserDto> |
||||
|
{ |
||||
|
public Guid BlogId { get; set; } |
||||
|
|
||||
|
public string Title { get; set; } |
||||
|
|
||||
|
public string Slug { get; set; } |
||||
|
|
||||
|
public string ShortDescription { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Content; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Blogs |
||||
|
{ |
||||
|
public interface IBlogPostPublicAppService |
||||
|
{ |
||||
|
Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input); |
||||
|
|
||||
|
Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug); |
||||
|
|
||||
|
Task<RemoteStreamContent> GetCoverImageAsync(Guid id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Content; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Blogs |
||||
|
{ |
||||
|
public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPublicAppService |
||||
|
{ |
||||
|
protected IBlogRepository BlogRepository { get; } |
||||
|
|
||||
|
protected IBlogPostRepository BlogPostRepository { get; } |
||||
|
|
||||
|
protected IBlobContainer<BlogPostCoverImageContainer> BlobContainer { get; } |
||||
|
|
||||
|
public BlogPostPublicAppService( |
||||
|
IBlogRepository blogRepository, |
||||
|
IBlogPostRepository blogPostRepository, |
||||
|
IBlobContainer<BlogPostCoverImageContainer> blobContainer) |
||||
|
{ |
||||
|
BlogRepository = blogRepository; |
||||
|
BlogPostRepository = blogPostRepository; |
||||
|
BlobContainer = blobContainer; |
||||
|
} |
||||
|
|
||||
|
public async Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug) |
||||
|
{ |
||||
|
var blog = await BlogRepository.GetBySlugAsync(blogSlug); |
||||
|
|
||||
|
var blogPost = await BlogPostRepository.GetBySlugAsync(blog.Id, blogPostSlug); |
||||
|
|
||||
|
return ObjectMapper.Map<BlogPost, BlogPostPublicDto>(blogPost); |
||||
|
} |
||||
|
|
||||
|
public async Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input) |
||||
|
{ |
||||
|
var blog = await BlogRepository.GetBySlugAsync(blogSlug); |
||||
|
|
||||
|
var blogPosts = await BlogPostRepository.GetPagedListAsync(blog.Id, input.SkipCount, input.MaxResultCount, input.Sorting); |
||||
|
|
||||
|
return new PagedResultDto<BlogPostPublicDto>( |
||||
|
await BlogPostRepository.GetCountAsync(blog.Id), |
||||
|
ObjectMapper.Map<List<BlogPost>, List<BlogPostPublicDto>>(blogPosts)); |
||||
|
} |
||||
|
|
||||
|
public async Task<RemoteStreamContent> GetCoverImageAsync(Guid id) |
||||
|
{ |
||||
|
var stream = await BlobContainer.GetAsync(id.ToString()); |
||||
|
|
||||
|
return new RemoteStreamContent(stream); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Content; |
||||
|
using Volo.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Public.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Blogs |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(BlogsFeature))] |
||||
|
[RemoteService(Name = CmsKitPublicRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area("cms-kit")] |
||||
|
[Route("api/cms-kit-public/blog-posts")] |
||||
|
public class BlogPostPublicController : CmsKitPublicControllerBase, IBlogPostPublicAppService |
||||
|
{ |
||||
|
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } |
||||
|
|
||||
|
public BlogPostPublicController(IBlogPostPublicAppService blogPostPublicAppService) |
||||
|
{ |
||||
|
BlogPostPublicAppService = blogPostPublicAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{blogSlug}/{blogPostSlug}")] |
||||
|
public Task<BlogPostPublicDto> GetAsync(string blogSlug, string blogPostSlug) |
||||
|
{ |
||||
|
return BlogPostPublicAppService.GetAsync(blogSlug, blogPostSlug); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}/cover-image")] |
||||
|
public Task<RemoteStreamContent> GetCoverImageAsync(Guid id) |
||||
|
{ |
||||
|
Response.Headers.Add("Content-Disposition", $"inline;filename=\"{id}\""); |
||||
|
Response.Headers.Add("Accept-Ranges", "bytes"); |
||||
|
Response.Headers.Add("Cache-Control", "max-age=120"); |
||||
|
Response.ContentType = "image"; |
||||
|
|
||||
|
return BlogPostPublicAppService.GetCoverImageAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{blogSlug}")] |
||||
|
public Task<PagedResultDto<BlogPostPublicDto>> GetListAsync(string blogSlug, PagedAndSortedResultRequestDto input) |
||||
|
{ |
||||
|
return BlogPostPublicAppService.GetListAsync(blogSlug, input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
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.Abp.GlobalFeatures; |
||||
|
using Volo.CmsKit.GlobalFeatures; |
||||
|
using Volo.CmsKit.Public.Pages; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Controllers |
||||
|
{ |
||||
|
[RequiresGlobalFeature(typeof(PagesFeature))] |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
@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.BlogPostComment |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs |
||||
|
@using Volo.CmsKit.Public.Web.Pages |
||||
|
@using Volo.Abp.GlobalFeatures |
||||
|
@using Volo.CmsKit.GlobalFeatures |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.ReactionSelection |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Rating |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Tags |
||||
|
|
||||
|
@inherits CmsKitPublicPageBase |
||||
|
|
||||
|
@model BlogPostModel |
||||
|
|
||||
|
@{ |
||||
|
PageLayout.Content.Title = Model.BlogPost.Title; |
||||
|
} |
||||
|
|
||||
|
@await Component.InvokeAsync(typeof(DefaultBlogPostViewComponent), new |
||||
|
{ |
||||
|
Model.BlogSlug, |
||||
|
Model.BlogPostSlug |
||||
|
}) |
||||
|
|
||||
|
@if (GlobalFeatureManager.Instance.IsEnabled<TagsFeature>()) |
||||
|
{ |
||||
|
@await Component.InvokeAsync(typeof(TagViewComponent), new |
||||
|
{ |
||||
|
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, |
||||
|
entityId = Model.BlogPost.Id.ToString() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
@if (GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>()) |
||||
|
{ |
||||
|
@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new |
||||
|
{ |
||||
|
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, |
||||
|
entityId = Model.BlogPost.Id.ToString() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
@if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>()) |
||||
|
{ |
||||
|
@await Component.InvokeAsync(typeof(RatingViewComponent), new |
||||
|
{ |
||||
|
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, |
||||
|
entityId = Model.BlogPost.Id.ToString() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
<hr /> |
||||
|
|
||||
|
@if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>()) |
||||
|
{ |
||||
|
@await Component.InvokeAsync(typeof(DefaultBlogPostCommentViewComponent), new |
||||
|
{ |
||||
|
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, |
||||
|
entityId = Model.BlogPost.Id.ToString() |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Public.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogPostModel : CmsKitPublicPageModelBase |
||||
|
{ |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string BlogSlug { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string BlogPostSlug { get; set; } |
||||
|
|
||||
|
public BlogPostPublicDto BlogPost { get; private set; } |
||||
|
|
||||
|
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } |
||||
|
|
||||
|
public BlogPostModel(IBlogPostPublicAppService blogPostPublicAppService) |
||||
|
{ |
||||
|
BlogPostPublicAppService = blogPostPublicAppService; |
||||
|
} |
||||
|
|
||||
|
public async Task OnGetAsync() |
||||
|
{ |
||||
|
BlogPost = await BlogPostPublicAppService.GetAsync(BlogSlug, BlogPostSlug); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
@page "/blogs/{blogSlug}" |
||||
|
|
||||
|
@using Volo.CmsKit.Public.Web.Pages |
||||
|
|
||||
|
@inherits CmsKitPublicPageBase |
||||
|
|
||||
|
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs.IndexModel |
||||
|
|
||||
|
<abp-row id="blogs-container"> |
||||
|
@foreach (var blog in Model.Blogs.Items) |
||||
|
{ |
||||
|
<abp-column size="_12" class="m-3"> |
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-card-title> |
||||
|
@blog.Title |
||||
|
</abp-card-title> |
||||
|
<abp-card-subtitle> |
||||
|
@@@blog.Creator?.UserName |
||||
|
</abp-card-subtitle> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<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> |
||||
|
@blog.ShortDescription |
||||
|
</abp-card-text> |
||||
|
<abp-card-text> |
||||
|
<a href="/blogs/@Model.BlogSlug/@blog.Slug"> |
||||
|
<abp-button text="@L["Read"]" |
||||
|
button-type="Outline_Primary" /> |
||||
|
</a> |
||||
|
</abp-card-text> |
||||
|
</abp-card-body> |
||||
|
<abp-card-footer> |
||||
|
<abp-card-text> |
||||
|
@blog.CreationTime |
||||
|
</abp-card-text> |
||||
|
</abp-card-footer> |
||||
|
</abp-card> |
||||
|
</abp-column> |
||||
|
|
||||
|
} |
||||
|
</abp-row> |
||||
|
<abp-row> |
||||
|
<abp-paginator model="Model.PagerModel" /> |
||||
|
</abp-row> |
||||
|
|
||||
@ -0,0 +1,45 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; |
||||
|
using Volo.CmsKit.Public.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Blogs |
||||
|
{ |
||||
|
public class IndexModel : CmsKitPublicPageModelBase |
||||
|
{ |
||||
|
public const int PageSize = 12; |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string BlogSlug { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public int CurrentPage { get; set; } = 1; |
||||
|
|
||||
|
public PagedResultDto<BlogPostPublicDto> Blogs { get; private set; } |
||||
|
|
||||
|
public PagerModel PagerModel => new PagerModel(Blogs.TotalCount, Blogs.Items.Count, CurrentPage, PageSize, Request.Path.ToString()); |
||||
|
|
||||
|
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } |
||||
|
|
||||
|
public IndexModel(IBlogPostPublicAppService blogPostPublicAppService) |
||||
|
{ |
||||
|
BlogPostPublicAppService = blogPostPublicAppService; |
||||
|
} |
||||
|
|
||||
|
public async Task OnGetAsync() |
||||
|
{ |
||||
|
Blogs = await BlogPostPublicAppService.GetListAsync( |
||||
|
BlogSlug, |
||||
|
new PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
SkipCount = PageSize * (CurrentPage - 1), |
||||
|
MaxResultCount = PageSize |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
|
||||
}) |
|
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,33 @@ |
|||||
|
@model Volo.CmsKit.Public.Blogs.BlogPostPublicDto |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Contents |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Layout |
||||
|
@using Volo.CmsKit.Localization |
||||
|
@using Microsoft.Extensions.Localization |
||||
|
|
||||
|
@inject IStringLocalizer<CmsKitResource> L |
||||
|
|
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-card-title>@Model.Title</abp-card-title> |
||||
|
<abp-card-subtitle>@Model.Creator?.UserName</abp-card-subtitle> |
||||
|
</abp-card-header> |
||||
|
|
||||
|
<abp-card-body> |
||||
|
@*<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 |
||||
|
{ |
||||
|
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, |
||||
|
entityId = Model.Id.ToString() |
||||
|
}) |
||||
|
</abp-card-body> |
||||
|
<abp-card-footer> |
||||
|
<abp-card-subtitle>@Model.CreationTime</abp-card-subtitle> |
||||
|
@if (Model.LastModificationTime != null) |
||||
|
{ |
||||
|
<abp-card-text><i>@L["LastModification"].Value : @Model.LastModificationTime</i></abp-card-text> |
||||
|
} |
||||
|
</abp-card-footer> |
||||
|
</abp-card> |
||||
|
|
||||
|
|
||||
@ -0,0 +1,29 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.CmsKit.Public.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPost |
||||
|
{ |
||||
|
[ViewComponent(Name = "CmsDefaultBlogPost")] |
||||
|
public class DefaultBlogPostViewComponent : AbpViewComponent |
||||
|
{ |
||||
|
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } |
||||
|
|
||||
|
public DefaultBlogPostViewComponent(IBlogPostPublicAppService blogPostPublicAppService) |
||||
|
{ |
||||
|
BlogPostPublicAppService = blogPostPublicAppService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IViewComponentResult> InvokeAsync(string blogSlug, string blogPostSlug) |
||||
|
{ |
||||
|
var blogPost = await BlogPostPublicAppService.GetAsync(blogSlug, blogPostSlug); |
||||
|
|
||||
|
return View("~/Pages/CmsKit/Shared/Components/Blogs/BlogPost/Default.cshtml", blogPost); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting |
||||
|
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment |
||||
|
@using Volo.CmsKit.Localization |
||||
|
@using Microsoft.Extensions.Localization |
||||
|
|
||||
|
@inject IStringLocalizer<CmsKitResource> L |
||||
|
|
||||
|
@model DefaultBlogPostCommentViewComponent.DefaultBlogPostCommentViewModel |
||||
|
|
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-card-title>@L["Comments"]</abp-card-title> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
@await Component.InvokeAsync(typeof(CommentingViewComponent), new |
||||
|
{ |
||||
|
entityType = Model.EntityType, |
||||
|
entityId = Model.EntityId |
||||
|
}) |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.CmsKit.Public.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment |
||||
|
{ |
||||
|
[ViewComponent(Name = "CmsDefaultBlogPostComment")] |
||||
|
public class DefaultBlogPostCommentViewComponent : AbpViewComponent |
||||
|
{ |
||||
|
protected IBlogPostPublicAppService BlogPostPublicAppService; |
||||
|
|
||||
|
public DefaultBlogPostCommentViewComponent(IBlogPostPublicAppService blogPostPublicAppService) |
||||
|
{ |
||||
|
BlogPostPublicAppService = blogPostPublicAppService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IViewComponentResult> InvokeAsync(string entityType, string entityId) |
||||
|
{ |
||||
|
return View( |
||||
|
"~/Pages/CmsKit/Shared/Components/Blogs/BlogPostComment/Default.cshtml", |
||||
|
new DefaultBlogPostCommentViewModel |
||||
|
{ |
||||
|
EntityType = entityType, |
||||
|
EntityId = entityId |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public class DefaultBlogPostCommentViewModel |
||||
|
{ |
||||
|
public string EntityType { get; set; } |
||||
|
public string EntityId { get; set; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Layout; |
||||
|
using Volo.CmsKit.Localization; |
||||
|
|
||||
|
namespace Volo.CmsKit.Public.Web.Pages |
||||
|
{ |
||||
|
public class CmsKitPublicPageBase : Microsoft.AspNetCore.Mvc.RazorPages.Page |
||||
|
{ |
||||
|
[RazorInject] |
||||
|
public IStringLocalizer<CmsKitResource> L { get; set; } |
||||
|
|
||||
|
[RazorInject] |
||||
|
public IPageLayout PageLayout { get; set; } |
||||
|
|
||||
|
public override Task ExecuteAsync() |
||||
|
{ |
||||
|
return Task.CompletedTask; // Will be overriden by razor pages. (.cshtml)
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "http://localhost:60873/", |
||||
|
"sslPort": 44300 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"Volo.CmsKit.Public.Web": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
}) |
||||
@ -0,0 +1,225 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Castle.DynamicProxy.Generators; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Content; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.CmsKit.Admin.Blogs; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogPostAdminAppService_Tests : CmsKitApplicationTestBase |
||||
|
{ |
||||
|
private readonly IBlogPostAdminAppService blogPostAdminAppService; |
||||
|
private readonly CmsKitTestData cmsKitTestData; |
||||
|
private readonly IBlogPostRepository blogPostRepository; |
||||
|
|
||||
|
public BlogPostAdminAppService_Tests() |
||||
|
{ |
||||
|
blogPostAdminAppService = GetRequiredService<IBlogPostAdminAppService>(); |
||||
|
cmsKitTestData = GetRequiredService<CmsKitTestData>(); |
||||
|
blogPostRepository = GetRequiredService<IBlogPostRepository>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CreateAsync_ShouldWorkProperly_WithCorrectData() |
||||
|
{ |
||||
|
var title = "My awesome new Post"; |
||||
|
var slug = "my-awesome-new-post"; |
||||
|
var shortDescription = "This blog is all about awesomeness 🤗!"; |
||||
|
|
||||
|
var created = await blogPostAdminAppService.CreateAsync(new CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
BlogId = cmsKitTestData.Blog_Id, |
||||
|
Title = title, |
||||
|
Slug = slug, |
||||
|
ShortDescription = shortDescription |
||||
|
}); |
||||
|
|
||||
|
created.Id.ShouldNotBe(Guid.Empty); |
||||
|
|
||||
|
var blogPost = await blogPostRepository.GetAsync(created.Id); |
||||
|
|
||||
|
blogPost.Title.ShouldBe(title); |
||||
|
blogPost.Slug.ShouldBe(slug); |
||||
|
blogPost.ShortDescription.ShouldBe(shortDescription); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CreateAsync_ShouldThrowException_WithNonExistingBlogId() |
||||
|
{ |
||||
|
var title = "Another My Awesome New Post"; |
||||
|
var slug = "another-my-awesome-new-post"; |
||||
|
var shortDescription = "This blog is all about awesomeness 🤗!"; |
||||
|
|
||||
|
var dto = new CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
// Non-existing Id
|
||||
|
BlogId = Guid.NewGuid(), |
||||
|
Title = title, |
||||
|
Slug = slug, |
||||
|
ShortDescription = shortDescription |
||||
|
}; |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostAdminAppService.CreateAsync(dto)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(Blog)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetAsync_ShouldWorkProperly_WithExistingId() |
||||
|
{ |
||||
|
var blogPost = await blogPostAdminAppService.GetAsync(cmsKitTestData.BlogPost_1_Id); |
||||
|
|
||||
|
blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title); |
||||
|
blogPost.Slug.ShouldBe(cmsKitTestData.BlogPost_1_Slug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetAsync_ShouldThrowException_WithNonExistingId() |
||||
|
{ |
||||
|
var nonExistingId = Guid.NewGuid(); |
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostAdminAppService.GetAsync(nonExistingId)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(BlogPost)); |
||||
|
exception.Id.ShouldBe(nonExistingId); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldWorkProperly_WithExistingSlug() |
||||
|
{ |
||||
|
var blogPost = await blogPostAdminAppService.GetBySlugAsync(cmsKitTestData.BlogSlug, cmsKitTestData.BlogPost_1_Slug); |
||||
|
|
||||
|
blogPost.Id.ShouldBe(cmsKitTestData.BlogPost_1_Id); |
||||
|
blogPost.Title.ShouldBe(cmsKitTestData.BlogPost_1_Title); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogPostSlug() |
||||
|
{ |
||||
|
var nonExistingSlug = "any-other-url"; |
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostAdminAppService.GetBySlugAsync(cmsKitTestData.BlogSlug, nonExistingSlug)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(BlogPost)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogSlug() |
||||
|
{ |
||||
|
var nonExistingSlug = "any-other-url"; |
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostAdminAppService.GetBySlugAsync(nonExistingSlug, cmsKitTestData.Page_1_Url)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(Blog)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetListAsync_ShouldWorkProperly_WithDefaultParameters() |
||||
|
{ |
||||
|
var list = await blogPostAdminAppService.GetListAsync(new PagedAndSortedResultRequestDto()); |
||||
|
|
||||
|
list.ShouldNotBeNull(); |
||||
|
list.TotalCount.ShouldBe(2); |
||||
|
list.Items.ShouldNotBeEmpty(); |
||||
|
list.Items.Count.ShouldBe(2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task UpdateAsync_ShouldWorkProperly_WithRegularDatas() |
||||
|
{ |
||||
|
var shortDescription = "Another short description"; |
||||
|
var title = "[Solved] Another Blog Post"; |
||||
|
var slug = "another-short-blog-post"; |
||||
|
|
||||
|
await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, new CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
BlogId = cmsKitTestData.Blog_Id, |
||||
|
ShortDescription = shortDescription, |
||||
|
Title = title, |
||||
|
Slug = slug, |
||||
|
}); |
||||
|
|
||||
|
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_2_Id); |
||||
|
|
||||
|
blogPost.Title.ShouldBe(title); |
||||
|
blogPost.ShortDescription.ShouldBe(shortDescription); |
||||
|
blogPost.Slug.ShouldBe(slug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task UpdateAsync_ShouldThrowException_WhileChangingWithNonExistingBlogId() |
||||
|
{ |
||||
|
var nonExistingId = Guid.NewGuid(); |
||||
|
|
||||
|
var dto = new CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
BlogId = nonExistingId, |
||||
|
Title = cmsKitTestData.Page_2_Title, |
||||
|
Slug = cmsKitTestData.BlogPost_2_Slug |
||||
|
}; |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, dto)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(Blog)); |
||||
|
exception.Id.ShouldBe(nonExistingId); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task UpdateAsync_ShouldThrowException_WhileUpdatingWithAlreadyExistingSlug() |
||||
|
{ |
||||
|
var dto = new CreateUpdateBlogPostDto |
||||
|
{ |
||||
|
BlogId = cmsKitTestData.Blog_Id, |
||||
|
Title = "Some new title", |
||||
|
Slug = cmsKitTestData.BlogPost_1_Slug |
||||
|
}; |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () => |
||||
|
await blogPostAdminAppService.UpdateAsync(cmsKitTestData.BlogPost_2_Id, dto)); |
||||
|
|
||||
|
exception.Slug.ShouldBe(cmsKitTestData.BlogPost_1_Slug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DeleteAsync_ShouldWorkProperly_WithExistingId() |
||||
|
{ |
||||
|
await blogPostAdminAppService.DeleteAsync(cmsKitTestData.Page_2_Id); |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>(async () => |
||||
|
await blogPostRepository.GetAsync(cmsKitTestData.Page_2_Id)); |
||||
|
|
||||
|
exception.EntityType.ShouldBe(typeof(BlogPost)); |
||||
|
exception.Id.ShouldBe(cmsKitTestData.Page_2_Id); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SetCoverImage_ShouldNotThrowException_WithCorrectData() |
||||
|
{ |
||||
|
using (var imageStream = GetSampleImageStream()) |
||||
|
{ |
||||
|
await WithUnitOfWorkAsync(async () => |
||||
|
{ |
||||
|
await blogPostAdminAppService.SetCoverImageAsync( |
||||
|
cmsKitTestData.BlogPost_2_Id, |
||||
|
new RemoteStreamContent(imageStream)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Stream GetSampleImageStream() |
||||
|
{ |
||||
|
var assembly = GetType().Assembly; |
||||
|
var resourceName = "Volo.CmsKit.Data.BlogPostSample.png"; |
||||
|
|
||||
|
return assembly.GetManifestResourceStream(resourceName); |
||||
|
} |
||||
|
// SetCoverImage
|
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 6.8 KiB |
@ -0,0 +1,92 @@ |
|||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.CmsKit.Tags; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class BlogPostManager_Tests : CmsKitDomainTestBase |
||||
|
{ |
||||
|
private readonly IBlogPostManager blogPostManager; |
||||
|
private readonly IGuidGenerator guidGenerator; |
||||
|
private readonly IBlogPostRepository blogPostRepository; |
||||
|
private readonly CmsKitTestData cmsKitTestData; |
||||
|
|
||||
|
public BlogPostManager_Tests() |
||||
|
{ |
||||
|
this.blogPostManager = GetRequiredService<IBlogPostManager>(); |
||||
|
this.guidGenerator = GetRequiredService<IGuidGenerator>(); |
||||
|
this.blogPostRepository = GetRequiredService<IBlogPostRepository>(); |
||||
|
this.cmsKitTestData = GetRequiredService<CmsKitTestData>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CreateAsync_ShouldWorkProperly_WithCorrectData() |
||||
|
{ |
||||
|
var title = "New blog post"; |
||||
|
var slug = "new-blog-post"; |
||||
|
|
||||
|
var created = await blogPostManager.CreateAsync( |
||||
|
new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, title, slug)); |
||||
|
|
||||
|
created.Id.ShouldNotBe(Guid.Empty); |
||||
|
|
||||
|
var blogPost = await blogPostRepository.GetAsync(created.Id); |
||||
|
blogPost.Title.ShouldBe(title); |
||||
|
blogPost.Slug.ShouldBe(slug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CreateAsync_ShouldThrowException_WhenSlugAlreadyExists() |
||||
|
{ |
||||
|
var blogPost = new BlogPost(guidGenerator.Create(), cmsKitTestData.Blog_Id, "Any New Title", cmsKitTestData.BlogPost_1_Slug); |
||||
|
|
||||
|
await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () => |
||||
|
await blogPostManager.CreateAsync(blogPost)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task UpdateAsync_ShoudlWorkProperly_WithCorrectData() |
||||
|
{ |
||||
|
var newTitle = "Yet Another Post"; |
||||
|
|
||||
|
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); |
||||
|
|
||||
|
blogPost.SetTitle(newTitle); |
||||
|
|
||||
|
await blogPostManager.UpdateAsync(blogPost); |
||||
|
|
||||
|
var updated = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); |
||||
|
updated.Title.ShouldBe(newTitle); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SetSlugAsync_ShouldWorkProperly_WithNonExistingSlug() |
||||
|
{ |
||||
|
var newSlug = "yet-another-post"; |
||||
|
|
||||
|
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); |
||||
|
|
||||
|
await blogPostManager.SetSlugUrlAsync(blogPost, newSlug); |
||||
|
|
||||
|
blogPost.Slug.ShouldBe(newSlug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SetSlugAsync_ShouldThrowException_WithExistingSlug() |
||||
|
{ |
||||
|
var blogPost = await blogPostRepository.GetAsync(cmsKitTestData.BlogPost_1_Id); |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<BlogPostSlugAlreadyExistException>(async () => |
||||
|
await blogPostManager.SetSlugUrlAsync(blogPost, cmsKitTestData.BlogPost_2_Slug)); |
||||
|
|
||||
|
exception.BlogId.ShouldBe(blogPost.BlogId); |
||||
|
exception.Slug.ShouldBe(cmsKitTestData.BlogPost_2_Slug); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
using Volo.CmsKit.Blogs.Extensions; |
||||
|
using Shouldly; |
||||
|
using Unidecode.NET; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public class SlugExtensions_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "My awesome name"; |
||||
|
var expected = "my-awesome-name"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithDot() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "My Perfect Title v.2"; |
||||
|
var expected = "my-perfect-title-v.2"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithQuestionMark() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "Are you gonna die?"; |
||||
|
var expected = "are-you-gonna-die"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithAmpersand() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "We & Machines Challenge"; |
||||
|
var expected = "we-machines-challenge"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithDoubleDash() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "Go and Code --part 2"; |
||||
|
var expected = "go-and-code-part-2"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithCyrillicChars() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "Мое классное название"; |
||||
|
var expected = "moe-klassnoe-nazvanie"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithTurkishChars() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "Özel Türkçe karakterler: ğüşiöç"; |
||||
|
var expected = "ozel-turkce-karakterler-gusioc"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithChineseChars() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "我的真棒头衔"; |
||||
|
var expected = "wo-de-zhen-bang-tou-xian"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeSlug_ShouldWorkProperly_WithEmoji() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var name = "Let's Rock 👊"; |
||||
|
var expected = "lets-rock"; |
||||
|
|
||||
|
// Act
|
||||
|
var actual = name.NormalizeSlug(); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.ShouldBe(expected); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Volo.CmsKit.Pages; |
||||
|
|
||||
|
namespace Volo.CmsKit.EntityFrameworkCore.Blogs |
||||
|
{ |
||||
|
public class BlogPostRepository_Test : BlogPostRepository_Test<CmsKitEntityFrameworkCoreTestModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
|
||||
|
namespace Volo.CmsKit.EntityFrameworkCore.Blogs |
||||
|
{ |
||||
|
public class BlogRepository_Test : BlogRepository_Test<CmsKitEntityFrameworkCoreTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Tags; |
||||
|
|
||||
|
namespace Volo.CmsKit.EntityFrameworkCore.Tags |
||||
|
{ |
||||
|
public class EntityTagRepository_Test : EntityTagRepository_Test<CmsKitEntityFrameworkCoreTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.MongoDB.Blogs |
||||
|
{ |
||||
|
[Collection(MongoTestCollection.Name)] |
||||
|
public class BlogPostRepository_Test : BlogPostRepository_Test<CmsKitMongoDbTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Blogs; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.MongoDB.Blogs |
||||
|
{ |
||||
|
[Collection(MongoTestCollection.Name)] |
||||
|
public class BlogRepository_Test : BlogRepository_Test<CmsKitMongoDbTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.CmsKit.Tags; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.MongoDB.Tags |
||||
|
{ |
||||
|
[Collection(MongoTestCollection.Name)] |
||||
|
public class EntityTagRepository_Test : EntityTagRepository_Test<CmsKitMongoDbTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public abstract class BlogPostRepository_Test<TStartupModule> : CmsKitTestBase<TStartupModule> |
||||
|
where TStartupModule : IAbpModule |
||||
|
{ |
||||
|
private readonly CmsKitTestData testData; |
||||
|
private readonly IBlogPostRepository blogPostRepository; |
||||
|
|
||||
|
public BlogPostRepository_Test() |
||||
|
{ |
||||
|
testData = GetRequiredService<CmsKitTestData>(); |
||||
|
blogPostRepository = GetRequiredService<IBlogPostRepository>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SlugExistsAsync_ShouldReturnTrue_WithExistingSlug() |
||||
|
{ |
||||
|
var result = await blogPostRepository.SlugExistsAsync(testData.Blog_Id, testData.BlogPost_1_Slug); |
||||
|
|
||||
|
result.ShouldBeTrue(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SlugExistsAsync_ShouldReturnFalse_WithNonExistingSlug() |
||||
|
{ |
||||
|
var nonExistingSlug = "any-other-url-slug"; |
||||
|
|
||||
|
var result = await blogPostRepository.SlugExistsAsync(testData.Blog_Id, nonExistingSlug); |
||||
|
|
||||
|
result.ShouldBeFalse(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SlugExistsAsync_ShouldReturnFalse_WithNonExistingBlogId() |
||||
|
{ |
||||
|
var nonExistingBlogId = Guid.NewGuid(); |
||||
|
|
||||
|
var result = await blogPostRepository.SlugExistsAsync(nonExistingBlogId, testData.BlogPost_1_Slug); |
||||
|
|
||||
|
result.ShouldBeFalse(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldWorkProperly_WithCorrectParameters() |
||||
|
{ |
||||
|
var blogPost = await blogPostRepository.GetBySlugAsync(testData.Blog_Id, testData.BlogPost_1_Slug); |
||||
|
|
||||
|
blogPost.ShouldNotBeNull(); |
||||
|
blogPost.Id.ShouldBe(testData.BlogPost_1_Id); |
||||
|
blogPost.Slug.ShouldBe(testData.BlogPost_1_Slug); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogPostSlug() |
||||
|
{ |
||||
|
var nonExistingSlugUrl = "absolutely-non-existing-url"; |
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>( |
||||
|
async () => await blogPostRepository.GetBySlugAsync(testData.Blog_Id, nonExistingSlugUrl)); |
||||
|
|
||||
|
exception.ShouldNotBeNull(); |
||||
|
exception.EntityType.ShouldBe(typeof(BlogPost)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingBlogId() |
||||
|
{ |
||||
|
var nonExistingBlogId = Guid.NewGuid(); |
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>( |
||||
|
async () => await blogPostRepository.GetBySlugAsync(nonExistingBlogId, testData.BlogPost_1_Slug)); |
||||
|
|
||||
|
exception.ShouldNotBeNull(); |
||||
|
exception.EntityType.ShouldBe(typeof(BlogPost)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetPagedListAsync_ShouldWorkProperly_WithBlogId_WhileGetting10_WithoutSorting() |
||||
|
{ |
||||
|
var result = await blogPostRepository.GetPagedListAsync(testData.Blog_Id, 0, 10, default); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.ShouldNotBeEmpty(); |
||||
|
result.Count.ShouldBe(2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetPagedListAsync_ShouldWorkProperly_WithBlogId_WhileGetting1_WithoutSorting() |
||||
|
{ |
||||
|
var result = await blogPostRepository.GetPagedListAsync(testData.Blog_Id, default, 1, default); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.ShouldNotBeEmpty(); |
||||
|
result.Count.ShouldBe(1); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetPagedListAsync_ShouldWorkProperly_WithBlogId_WhileGetting1InPage2_WithoutSorting() |
||||
|
{ |
||||
|
var result = await blogPostRepository.GetPagedListAsync(testData.Blog_Id, 1, 1, default); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.ShouldNotBeEmpty(); |
||||
|
result.Count.ShouldBe(1); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetPagedListAsync_ShouldWorkProperly_WithBlogId_WhileGetting10_WithSortingByTitle() |
||||
|
{ |
||||
|
var result = await blogPostRepository.GetPagedListAsync(testData.Blog_Id, default, 10, nameof(BlogPost.Title)); |
||||
|
|
||||
|
result.ShouldNotBeNull(); |
||||
|
result.ShouldNotBeEmpty(); |
||||
|
result.Count.ShouldBe(2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.CmsKit.Blogs |
||||
|
{ |
||||
|
public abstract class BlogRepository_Test<TStartupModule> : CmsKitTestBase<TStartupModule> |
||||
|
where TStartupModule : IAbpModule |
||||
|
{ |
||||
|
private readonly CmsKitTestData testData; |
||||
|
private readonly IBlogRepository blogRepository; |
||||
|
|
||||
|
public BlogRepository_Test() |
||||
|
{ |
||||
|
this.testData = GetRequiredService<CmsKitTestData>(); |
||||
|
this.blogRepository = GetRequiredService<IBlogRepository>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldWorkProperly_WithExistingSlug() |
||||
|
{ |
||||
|
var blog = await blogRepository.GetBySlugAsync(testData.BlogSlug); |
||||
|
|
||||
|
blog.ShouldNotBeNull(); |
||||
|
blog.Slug.ShouldBe(testData.BlogSlug); |
||||
|
blog.Id.ShouldBe(testData.Blog_Id); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetBySlugAsync_ShouldThrowException_WithNonExistingSlug() |
||||
|
{ |
||||
|
var nonExistingSlug = "some-blog-slug-that-doesnt-exist"; |
||||
|
|
||||
|
var exception = await Should.ThrowAsync<EntityNotFoundException>( |
||||
|
async () => await blogRepository.GetBySlugAsync(nonExistingSlug)); |
||||
|
|
||||
|
exception.ShouldNotBeNull(); |
||||
|
exception.EntityType.ShouldBe(typeof(Blog)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ExistsAsync_ShouldReturnTrue_WithExistingId() |
||||
|
{ |
||||
|
var result = await blogRepository.ExistsAsync(testData.Blog_Id); |
||||
|
|
||||
|
result.ShouldBeTrue(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ExistsAsync_ShouldReturnFalse_WithExistingId() |
||||
|
{ |
||||
|
var nonExistingId = Guid.NewGuid(); |
||||
|
|
||||
|
var result = await blogRepository.ExistsAsync(nonExistingId); |
||||
|
|
||||
|
result.ShouldBeFalse(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace Volo.CmsKit |
||||
|
{ |
||||
|
public class CmsKitFakeCurrentUser : ITransientDependency, ICurrentUser |
||||
|
{ |
||||
|
private readonly CmsKitTestData cmsKitTestData; |
||||
|
|
||||
|
public CmsKitFakeCurrentUser(CmsKitTestData cmsKitTestData) |
||||
|
{ |
||||
|
this.cmsKitTestData = cmsKitTestData; |
||||
|
} |
||||
|
|
||||
|
public bool IsAuthenticated { get; } |
||||
|
public Guid? Id => cmsKitTestData.User1Id; |
||||
|
public string UserName => cmsKitTestData.User1UserName; |
||||
|
public string Name { get; } |
||||
|
public string SurName { get; } |
||||
|
public string PhoneNumber { get; } |
||||
|
public bool PhoneNumberVerified { get; } |
||||
|
public string Email { get; } |
||||
|
public bool EmailVerified { get; } |
||||
|
public Guid? TenantId { get; } |
||||
|
public string[] Roles { get; } |
||||
|
|
||||
|
public Claim FindClaim(string claimType) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Claim[] FindClaims(string claimType) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Claim[] GetAllClaims() |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public bool IsInRole(string roleName) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue