diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs index 35d6d1c97e..1e363c8578 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs @@ -18,5 +18,7 @@ namespace Volo.Blogging.Admin.Blogs Task UpdateAsync(Guid id, UpdateBlogDto input); Task DeleteAsync(Guid id); + + Task ClearCacheAsync(Guid id); } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs b/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs index b11bd481fe..1c5f282d7c 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs @@ -3,18 +3,22 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; +using Volo.Abp.Caching; using Volo.Blogging.Blogs; using Volo.Blogging.Blogs.Dtos; +using Volo.Blogging.Posts; namespace Volo.Blogging.Admin.Blogs { public class BlogManagementAppService : BloggingAdminAppServiceBase, IBlogManagementAppService { private readonly IBlogRepository _blogRepository; - - public BlogManagementAppService(IBlogRepository blogRepository) + private readonly IDistributedCache> _postsCache; + + public BlogManagementAppService(IBlogRepository blogRepository, IDistributedCache> postsCache) { _blogRepository = blogRepository; + _postsCache = postsCache; } public async Task> GetListAsync() @@ -63,5 +67,11 @@ namespace Volo.Blogging.Admin.Blogs { await _blogRepository.DeleteAsync(id); } + + [Authorize(BloggingPermissions.Blogs.ClearCache)] + public async Task ClearCacheAsync(Guid id) + { + await _postsCache.RemoveAsync(id.ToString()); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs b/modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs index 815604e871..ce6457dcc7 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs @@ -54,5 +54,12 @@ namespace Volo.Blogging.Admin { await _blogManagementAppService.DeleteAsync(id); } + + [HttpGet] + [Route("clear-cache/{id}")] + public async Task ClearCacheAsync(Guid id) + { + await _blogManagementAppService.ClearCacheAsync(id); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js index a2b618b89f..aaa378cabd 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js @@ -52,6 +52,22 @@ }); }, }, + { + text: l("ClearCache"), + visible: abp.auth.isGranted( + 'Blogging.Blog.ClearCache' + ), + confirmMessage: function (data) { + return l("ClearCacheConfirmationMessage"); + }, + action: function (data) { + volo.blogging.admin.blogManagement + .clearCache(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }) + } + } ], }, }, diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs index 65a6ab1ab7..595e32199c 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs @@ -15,6 +15,7 @@ namespace Volo.Blogging blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit")); blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete")); blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create")); + blogs.AddChild(BloggingPermissions.Blogs.ClearCache, L("Permission:ClearCache")); var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts")); posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit")); diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs index fae85acf88..617b4d06f1 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs @@ -13,6 +13,7 @@ namespace Volo.Blogging public const string Delete = Default + ".Delete"; public const string Update = Default + ".Update"; public const string Create = Default + ".Create"; + public const string ClearCache = Default + ".ClearCache"; } public static class Posts diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs index b9e3cf0dea..3572e00113 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs @@ -20,6 +20,13 @@ namespace Volo.Blogging CreateMap().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount).Ignore(x=>x.Tags); CreateMap().Ignore(x => x.Writer); CreateMap(); + CreateMap().Ignore(x=>x.CommentCount).Ignore(x=>x.Tags); + CreateMap() + .IgnoreModificationAuditedObjectProperties() + .IgnoreDeletionAuditedObjectProperties() + .Ignore(x=>x.Writer) + .Ignore(x => x.CommentCount) + .Ignore(x => x.Tags); } } } diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs index 07d22d3385..7b42d25f84 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Caching.Distributed; using Volo.Abp.Caching; +using Volo.Abp.EventBus.Local; using Volo.Blogging.Comments; using Volo.Blogging.Tagging; using Volo.Blogging.Tagging.Dtos; @@ -20,41 +21,92 @@ namespace Volo.Blogging.Posts private readonly IPostRepository _postRepository; private readonly ITagRepository _tagRepository; private readonly ICommentRepository _commentRepository; - private readonly IDistributedCache> _postsCache; - - public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService, IDistributedCache> postsCache) + private readonly IDistributedCache> _postsCache; + private readonly ILocalEventBus _localEventBus; + + public PostAppService( + IPostRepository postRepository, + ITagRepository tagRepository, + ICommentRepository commentRepository, + IBlogUserLookupService userLookupService, + IDistributedCache> postsCache, + ILocalEventBus localEventBus + ) { UserLookupService = userLookupService; _postRepository = postRepository; _tagRepository = tagRepository; _commentRepository = commentRepository; _postsCache = postsCache; + _localEventBus = localEventBus; } public async Task> GetListByBlogIdAndTagName(Guid id, string tagName) { - var cacheKey = id.ToString() + "-" + tagName; + var posts = await _postRepository.GetPostsByBlogId(id); + var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName); + var userDictionary = new Dictionary(); + var postDtos = new List(ObjectMapper.Map, List>(posts)); - return await _postsCache.GetOrAddAsync( - cacheKey, - async () => await GetPostsByBlogIdAndTagName(id, tagName), - () => new DistributedCacheEntryOptions + foreach (var postDto in postDtos) + { + postDto.Tags = await GetTagsOfPost(postDto.Id); + } + + if (tag != null) + { + postDtos = await FilterPostsByTag(postDtos, tag); + } + + foreach (var postDto in postDtos) + { + if (postDto.CreatorId.HasValue) { - AbsoluteExpiration = DateTimeOffset.Now.AddHours(6) + if (!userDictionary.ContainsKey(postDto.CreatorId.Value)) + { + var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); + if (creatorUser != null) + { + userDictionary[creatorUser.Id] = ObjectMapper.Map(creatorUser); + } + } + + if (userDictionary.ContainsKey(postDto.CreatorId.Value)) + { + postDto.Writer = userDictionary[(Guid)postDto.CreatorId]; + } } - ); + } + + return new ListResultDto(postDtos); } public async Task> GetTimeOrderedListAsync(Guid blogId) { - return await _postsCache.GetOrAddAsync( + var postCacheItems = await _postsCache.GetOrAddAsync( blogId.ToString(), async () => await GetTimeOrderedPostsAsync(blogId), () => new DistributedCacheEntryOptions { - AbsoluteExpiration = DateTimeOffset.Now.AddHours(6) + AbsoluteExpiration = DateTimeOffset.Now.AddHours(1) } ); + + var postsWithDetails = ObjectMapper.Map, List>(postCacheItems); + + foreach (var post in postsWithDetails) + { + if (post.CreatorId.HasValue) + { + var creatorUser = await UserLookupService.FindByIdAsync(post.CreatorId.Value); + if (creatorUser != null) + { + post.Writer = ObjectMapper.Map(creatorUser); + } + } + } + + return new ListResultDto(postsWithDetails); } public async Task GetForReadingAsync(GetPostInput input) @@ -107,6 +159,7 @@ namespace Volo.Blogging.Posts await _commentRepository.DeleteOfPost(id); await _postRepository.DeleteAsync(id); + await PublishPostChangedEventAsync(post.BlogId); } [Authorize(BloggingPermissions.Posts.Update)] @@ -128,6 +181,7 @@ namespace Volo.Blogging.Posts var tagList = SplitTags(input.Tags); await SaveTags(tagList, post); + await PublishPostChangedEventAsync(post.BlogId); return ObjectMapper.Map(post); } @@ -153,66 +207,16 @@ namespace Volo.Blogging.Posts var tagList = SplitTags(input.Tags); await SaveTags(tagList, post); + await PublishPostChangedEventAsync(post.BlogId); return ObjectMapper.Map(post); } - private async Task> GetPostsByBlogIdAndTagName(Guid id, string tagName) - { - var posts = await _postRepository.GetPostsByBlogId(id); - var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName); - var userDictionary = new Dictionary(); - var postDtos = new List(ObjectMapper.Map, List>(posts)); - - foreach (var postDto in postDtos) - { - postDto.Tags = await GetTagsOfPost(postDto.Id); - } - - if (tag != null) - { - postDtos = await FilterPostsByTag(postDtos, tag); - } - - foreach (var postDto in postDtos) - { - if (postDto.CreatorId.HasValue) - { - if (!userDictionary.ContainsKey(postDto.CreatorId.Value)) - { - var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); - if (creatorUser != null) - { - userDictionary[creatorUser.Id] = ObjectMapper.Map(creatorUser); - } - } - - if (userDictionary.ContainsKey(postDto.CreatorId.Value)) - { - postDto.Writer = userDictionary[(Guid)postDto.CreatorId]; - } - } - } - - return new ListResultDto(postDtos); - } - - private async Task> GetTimeOrderedPostsAsync(Guid blogId) + private async Task> GetTimeOrderedPostsAsync(Guid blogId) { var posts = await _postRepository.GetOrderedList(blogId); - var postDtos = new List(ObjectMapper.Map, List>(posts)); - - foreach (var postDto in postDtos) - { - var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); - if (creatorUser != null) - { - postDto.Writer = ObjectMapper.Map(creatorUser); - } - } - - return new ListResultDto(postDtos); + return ObjectMapper.Map, List>(posts); } private async Task RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null) @@ -300,5 +304,14 @@ namespace Volo.Blogging.Posts return Task.FromResult(filteredPostDtos); } + + private async Task PublishPostChangedEventAsync(Guid blogId) + { + await _localEventBus.PublishAsync( + new PostChangedEvent + { + BlogId = blogId + }); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json index 5ebdf25d9f..2219aa9fcd 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json @@ -12,6 +12,7 @@ "Permission:Posts": "Posts", "Permission:Tags": "Tags", "Permission:Comments": "Comments", + "Permission:ClearCache": "Clear cache", "Title": "Title", "Delete": "Delete", "Reply": "Reply", @@ -53,6 +54,8 @@ "Blogs": "Blogs", "Tags": "Tags", "ShareOn": "Share on", - "TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!" + "TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!", + "ClearCache": "Clear cache", + "ClearCacheConfirmationMessage": "Are you sure you want to clear the cache?" } } diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json index 4a9a38b040..915e8b233e 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json @@ -12,6 +12,7 @@ "Permission:Posts": "Yazılar", "Permission:Tags": "Etiketler", "Permission:Comments": "Yorumlar", + "Permission:ClearCache": "Önbelleği temizle", "Title": "Başlık", "Delete": "Sil", "Reply": "Yanıtla", @@ -53,6 +54,8 @@ "Blogs": "Bloglar", "Tags": "Etiketler", "ShareOn": "Paylaş", - "TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!" + "TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!", + "ClearCache": "Önbelleği temizle", + "ClearCacheConfirmationMessage": "Önbelleği temizlemek istediğinizden emin misiniz?" } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj b/modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj index 73cf0498c5..c6c096105b 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj @@ -15,6 +15,7 @@ + diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs index 3179303215..2e5e9a68e0 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AutoMapper; +using Volo.Abp.Caching; using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; @@ -13,7 +14,9 @@ namespace Volo.Blogging [DependsOn( typeof(BloggingDomainSharedModule), typeof(AbpDddDomainModule), - typeof(AbpAutoMapperModule))] + typeof(AbpAutoMapperModule), + typeof(AbpCachingModule) + )] public class BloggingDomainModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs new file mode 100644 index 0000000000..e18e6de23b --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace Volo.Blogging.Posts +{ + public class PostCacheInvalidator : ILocalEventHandler, ITransientDependency + { + protected IDistributedCache> Cache { get; } + + public PostCacheInvalidator(IDistributedCache> cache) + { + Cache = cache; + } + + public virtual async Task HandleEventAsync(PostChangedEvent post) + { + await Cache.RemoveAsync(post.BlogId.ToString()); + } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs new file mode 100644 index 0000000000..d65b64ba43 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Blogging.Tagging; +using Volo.Blogging.Users; + +namespace Volo.Blogging.Posts +{ + [Serializable] + public class PostCacheItem : ICreationAuditedObject + { + public Guid Id { get; set; } + + public Guid BlogId { get; set; } + + public string Title { get; set; } + + public string CoverImage { get; set; } + + public string Url { get; set; } + + public string Content { get; set; } + + public string Description { get; set; } + + public int ReadCount { get; set; } + + public int CommentCount { get; set; } + + public List Tags { get; set; } + + public Guid? CreatorId { get; set; } + + public DateTime CreationTime { get; set; } + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostChangedEvent.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostChangedEvent.cs new file mode 100644 index 0000000000..c32ed92224 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostChangedEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Blogging.Posts +{ + public class PostChangedEvent + { + public Guid BlogId { get; set; } + } +} \ No newline at end of file