using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; 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; using Volo.Blogging.Users; using Volo.Abp.Data; namespace Volo.Blogging.Posts { public class PostAppService : BloggingAppServiceBase, IPostAppService { protected IBlogUserLookupService UserLookupService { get; } protected IPostRepository PostRepository { get; } protected ITagRepository TagRepository { get; } protected ICommentRepository CommentRepository { get; } protected IDistributedCache> PostsCache { get; } protected ILocalEventBus LocalEventBus { get; } 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 virtual async Task> GetListByBlogIdAndTagNameAsync(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 GetTagsOfPostAsync(postDto.Id); } if (tag != null) { postDtos = await FilterPostsByTagAsync(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); } public virtual async Task> GetTimeOrderedListAsync(Guid blogId) { var postCacheItems = await PostsCache.GetOrAddAsync( blogId.ToString(), async () => await GetTimeOrderedPostsAsync(blogId), () => new DistributedCacheEntryOptions { 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 virtual async Task GetForReadingAsync(GetPostInput input) { var post = await PostRepository.GetPostByUrl(input.BlogId, input.Url); post.IncreaseReadCount(); var postDto = ObjectMapper.Map(post); postDto.Tags = await GetTagsOfPostAsync(postDto.Id); if (postDto.CreatorId.HasValue) { var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); postDto.Writer = ObjectMapper.Map(creatorUser); } return postDto; } public virtual async Task GetAsync(Guid id) { var post = await PostRepository.GetAsync(id); var postDto = ObjectMapper.Map(post); postDto.Tags = await GetTagsOfPostAsync(postDto.Id); if (postDto.CreatorId.HasValue) { var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); postDto.Writer = ObjectMapper.Map(creatorUser); } return postDto; } [Authorize(BloggingPermissions.Posts.Delete)] public virtual async Task DeleteAsync(Guid id) { var post = await PostRepository.GetAsync(id); await AuthorizationService.CheckAsync(post, CommonOperations.Delete); var tags = await GetTagsOfPostAsync(id); await TagRepository.DecreaseUsageCountOfTagsAsync(tags.Select(t => t.Id).ToList()); await CommentRepository.DeleteOfPost(id); await PostRepository.DeleteAsync(id); await PublishPostChangedEventAsync(post.BlogId); } [Authorize(BloggingPermissions.Posts.Update)] public virtual async Task UpdateAsync(Guid id, UpdatePostDto input) { var post = await PostRepository.GetAsync(id); input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url, post); await AuthorizationService.CheckAsync(post, CommonOperations.Update); post.SetTitle(input.Title); post.SetUrl(input.Url); post.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); post.Content = input.Content; post.Description = input.Description; post.CoverImage = input.CoverImage; post = await PostRepository.UpdateAsync(post); var tagList = SplitTags(input.Tags); await SaveTags(tagList, post); await PublishPostChangedEventAsync(post.BlogId); return ObjectMapper.Map(post); } public virtual async Task> GetListByUserIdAsync(Guid userId) { var posts = await PostRepository.GetListByUserIdAsync(userId); return ObjectMapper.Map, List>(posts); } public virtual async Task> GetLatestBlogPostsAsync(Guid blogId, int count) { var posts = await PostRepository.GetLatestBlogPostsAsync(blogId, count); var userDictionary = new Dictionary(); var postDtos = new List(ObjectMapper.Map, List>(posts)); foreach (var postDto in postDtos) { if (!postDto.CreatorId.HasValue) { continue; } if (userDictionary.TryGetValue(postDto.CreatorId.Value, out var creatorUserDto)) { postDto.Writer = creatorUserDto; continue; } var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value); if (creatorUser != null) { postDto.Writer = ObjectMapper.Map(creatorUser); userDictionary[creatorUser.Id] = postDto.Writer; } } return new List(postDtos); } [Authorize(BloggingPermissions.Posts.Create)] public virtual async Task CreateAsync(CreatePostDto input) { input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url); var post = new Post( id: GuidGenerator.Create(), blogId: input.BlogId, title: input.Title, coverImage: input.CoverImage, url: input.Url ) { Content = input.Content, Description = input.Description }; await PostRepository.InsertAsync(post); var tagList = SplitTags(input.Tags); await SaveTags(tagList, post); await PublishPostChangedEventAsync(post.BlogId); return ObjectMapper.Map(post); } private async Task> GetTimeOrderedPostsAsync(Guid blogId) { var posts = await PostRepository.GetOrderedList(blogId); return ObjectMapper.Map, List>(posts); } private async Task RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null) { if (await PostRepository.IsPostUrlInUseAsync(blogId, url, existingPost?.Id)) { return url + "-" + Guid.NewGuid().ToString().Substring(0, 5); } return url; } private async Task SaveTags(ICollection tags, Post post) { tags = tags .Select(t => t.ToLowerInvariant()) .Distinct() .ToList(); await RemoveOldTags(tags, post); await AddNewTags(tags, post); } private async Task RemoveOldTags(ICollection newTags, Post post) { foreach (var oldTag in post.Tags.ToList()) { var tag = await TagRepository.GetAsync(oldTag.TagId); var oldTagNameInNewTags = newTags.FirstOrDefault(t => t == tag.Name); if (oldTagNameInNewTags == null) { post.RemoveTag(oldTag.TagId); tag.DecreaseUsageCount(); await TagRepository.UpdateAsync(tag); } else { newTags.Remove(oldTagNameInNewTags); } } } private async Task AddNewTags(IEnumerable newTags, Post post) { var tags = await TagRepository.GetListAsync(post.BlogId); foreach (var newTag in newTags) { var tag = tags.FirstOrDefault(t => t.Name == newTag); if (tag == null) { tag = await TagRepository.InsertAsync(new Tag(GuidGenerator.Create(), post.BlogId, newTag, 1)); } else { tag.IncreaseUsageCount(); tag = await TagRepository.UpdateAsync(tag); } post.AddTag(tag.Id); } } private async Task> GetTagsOfPostAsync(Guid id) { var tagIds = (await PostRepository.GetAsync(id)).Tags; var tags = await TagRepository.GetListAsync(tagIds.Select(t => t.TagId)); return ObjectMapper.Map, List>(tags); } private List SplitTags(string tags) { if (tags.IsNullOrWhiteSpace()) { return new List(); } return new List(tags.Split(",").Select(t => t.Trim())); } private Task> FilterPostsByTagAsync(IEnumerable allPostDtos, Tag tag) { var filteredPostDtos = allPostDtos.Where(p => p.Tags?.Any(t => t.Id == tag.Id) ?? false).ToList(); return Task.FromResult(filteredPostDtos); } private async Task PublishPostChangedEventAsync(Guid blogId) { await LocalEventBus.PublishAsync( new PostChangedEvent { BlogId = blogId }); } } }