using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; namespace Erp.Platform.Forum; [Authorize] public class ForumAppService : PlatformAppService, IForumAppService { private readonly IRepository _categoryRepository; private readonly IRepository _topicRepository; private readonly IRepository _postRepository; private readonly IIdentityUserRepository _identityUserRepository; public ForumAppService( IRepository categoryRepository, IRepository topicRepository, IRepository postRepository, IIdentityUserRepository identityUserRepository) { _categoryRepository = categoryRepository; _topicRepository = topicRepository; _postRepository = postRepository; _identityUserRepository = identityUserRepository; } // Search functionality public async Task SearchAsync(SearchForumInput input) { var result = new ForumSearchResultDto { Categories = [], Topics = [], Posts = [], TotalCount = 0 }; if (string.IsNullOrWhiteSpace(input.Query)) return result; var query = input.Query.ToLower(); // Search in categories if (input.SearchInCategories) { var categoryQuery = await _categoryRepository.GetQueryableAsync(); var categories = await AsyncExecuter.ToListAsync( categoryQuery.Where(c => c.IsActive && (c.Name.ToLower().Contains(query) || c.Description.ToLower().Contains(query))) .Take(10) ); result.Categories = ObjectMapper.Map, List>(categories); } // Search in topics if (input.SearchInTopics) { var topicQuery = await _topicRepository.GetQueryableAsync(); var topics = await AsyncExecuter.ToListAsync( topicQuery.Where(t => t.Title.ToLower().Contains(query) || t.Content.ToLower().Contains(query) || t.AuthorName.ToLower().Contains(query)) .OrderByDescending(t => t.CreationTime) .Take(20) ); result.Topics = ObjectMapper.Map, List>(topics); } // Search in posts if (input.SearchInPosts) { var postQuery = await _postRepository.GetQueryableAsync(); var posts = await AsyncExecuter.ToListAsync( postQuery.Where(p => p.Content.ToLower().Contains(query) || p.AuthorName.ToLower().Contains(query)) .OrderByDescending(p => p.CreationTime) .Take(30) ); result.Posts = ObjectMapper.Map, List>(posts); } result.TotalCount = result.Categories.Count + result.Topics.Count + result.Posts.Count; return result; } // Category management public async Task> GetCategoriesAsync(GetCategoriesInput input) { var queryable = await _categoryRepository.GetQueryableAsync(); if (input.IsActive.HasValue) { queryable = queryable.Where(c => c.IsActive == input.IsActive.Value); } if (!string.IsNullOrWhiteSpace(input.Search)) { var search = input.Search.ToLower(); queryable = queryable.Where(c => c.Name.ToLower().Contains(search) || c.Description.ToLower().Contains(search)); } queryable = queryable.OrderBy(c => c.DisplayOrder); var totalCount = await AsyncExecuter.CountAsync(queryable); var skipCount = input.SkipCount >= 0 ? input.SkipCount : 0; var maxResultCount = input.MaxResultCount > 0 ? input.MaxResultCount : 10; var categories = await AsyncExecuter.ToListAsync( queryable.Skip(input.SkipCount).Take(input.MaxResultCount) ); return new PagedResultDto( totalCount, ObjectMapper.Map, List>(categories) ); } public async Task GetCategoryAsync(Guid id) { var category = await _categoryRepository.GetAsync(id); return ObjectMapper.Map(category); } public async Task GetCategoryBySlugAsync(string slug) { var category = await _categoryRepository.FirstOrDefaultAsync(c => c.Slug == slug); if (category == null) throw new EntityNotFoundException(typeof(ForumCategory), slug); return ObjectMapper.Map(category); } [Authorize("App.ForumManagement.Create")] public async Task CreateCategoryAsync(CreateForumCategoryDto input) { var category = new ForumCategory { Name = input.Name, Slug = input.Slug, Description = input.Description, Icon = input.Icon, DisplayOrder = input.DisplayOrder, IsActive = input.IsActive, IsLocked = input.IsLocked }; await _categoryRepository.InsertAsync(category); return ObjectMapper.Map(category); } [Authorize("App.ForumManagement.Update")] public async Task UpdateCategoryAsync(Guid id, UpdateForumCategoryDto input) { var category = await _categoryRepository.GetAsync(id); category.Name = input.Name; category.Slug = input.Slug; category.Description = input.Description; category.Icon = input.Icon; category.DisplayOrder = input.DisplayOrder; category.IsActive = input.IsActive; category.IsLocked = input.IsLocked; await _categoryRepository.UpdateAsync(category); return ObjectMapper.Map(category); } [Authorize("App.ForumManagement.Update")] public async Task UpdateCategoryLockAsync(Guid id) { var category = await _categoryRepository.GetAsync(id); category.IsLocked = !category.IsLocked; await _categoryRepository.UpdateAsync(category); return ObjectMapper.Map(category); } [Authorize("App.ForumManagement.Update")] public async Task UpdateCategoryActiveAsync(Guid id) { var category = await _categoryRepository.GetAsync(id); category.IsActive = !category.IsActive; await _categoryRepository.UpdateAsync(category); return ObjectMapper.Map(category); } [Authorize("App.ForumManagement.Delete")] public async Task DeleteCategoryAsync(Guid id) { // Delete all topics and posts in this category var topics = await _topicRepository.GetListAsync(t => t.CategoryId == id); var topicIds = topics.Select(t => t.Id).ToList(); if (topicIds.Any()) { await _postRepository.DeleteAsync(p => topicIds.Contains(p.TopicId)); await _topicRepository.DeleteAsync(t => t.CategoryId == id); } await _categoryRepository.DeleteAsync(id); } // Topic management public async Task> GetTopicsAsync(GetTopicsInput input) { var queryable = await _topicRepository.GetQueryableAsync(); if (input.CategoryId.HasValue) { queryable = queryable.Where(t => t.CategoryId == input.CategoryId.Value); } if (input.IsPinned.HasValue) { queryable = queryable.Where(t => t.IsPinned == input.IsPinned.Value); } if (input.IsSolved.HasValue) { queryable = queryable.Where(t => t.IsSolved == input.IsSolved.Value); } if (!string.IsNullOrWhiteSpace(input.Search)) { var search = input.Search.ToLower(); queryable = queryable.Where(t => t.Title.ToLower().Contains(search) || t.Content.ToLower().Contains(search)); } queryable = queryable.OrderByDescending(t => t.IsPinned) .ThenByDescending(t => t.CreationTime); var totalCount = await AsyncExecuter.CountAsync(queryable); var topics = await AsyncExecuter.ToListAsync( queryable.Skip(input.SkipCount).Take(input.MaxResultCount) ); return new PagedResultDto( totalCount, ObjectMapper.Map, List>(topics) ); } public async Task GetTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.ViewCount++; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task CreateTopicAsync(CreateForumTopicDto input) { var topic = new ForumTopic( GuidGenerator.Create(), input.Title, input.Content, input.CategoryId, CurrentUser.Id.Value, CurrentUser.Name, input.TenantId ) { IsPinned = input.IsPinned, IsLocked = input.IsLocked }; await _topicRepository.InsertAsync(topic); // Update category topic count var category = await _categoryRepository.GetAsync(input.CategoryId); category.TopicCount++; await _categoryRepository.UpdateAsync(category); return ObjectMapper.Map(topic); } public async Task UpdateTopicAsync(Guid id, UpdateForumTopicDto input) { var topic = await _topicRepository.GetAsync(id); topic.Title = input.Title; topic.Content = input.Content; topic.IsPinned = input.IsPinned; topic.IsLocked = input.IsLocked; topic.IsSolved = input.IsSolved; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task DeleteTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); // Delete all posts in this topic await _postRepository.DeleteAsync(p => p.TopicId == id); // Update category counts var category = await _categoryRepository.GetAsync(topic.CategoryId); category.TopicCount = Math.Max(0, category.TopicCount ?? 0 - 1); var postCount = await _postRepository.CountAsync(p => p.TopicId == id); category.PostCount = Math.Max(0, category.PostCount ?? 0 - postCount); await _categoryRepository.UpdateAsync(category); await _topicRepository.DeleteAsync(id); } // Post management public async Task> GetPostsAsync(GetPostsInput input) { var queryable = await _postRepository.GetQueryableAsync(); if (input.TopicId.HasValue) { queryable = queryable.Where(p => p.TopicId == input.TopicId.Value); // Increment view count var topic = await _topicRepository.GetAsync(input.TopicId.Value); } if (input.IsAcceptedAnswer.HasValue) { queryable = queryable.Where(p => p.IsAcceptedAnswer == input.IsAcceptedAnswer.Value); } if (!string.IsNullOrWhiteSpace(input.Search)) { var search = input.Search.ToLower(); queryable = queryable.Where(p => p.Content.ToLower().Contains(search)); } queryable = queryable.OrderBy(p => p.CreationTime); var totalCount = await AsyncExecuter.CountAsync(queryable); var posts = await AsyncExecuter.ToListAsync( queryable.Skip(input.SkipCount).Take(input.MaxResultCount) ); return new PagedResultDto( totalCount, ObjectMapper.Map, List>(posts) ); } public async Task GetPostAsync(Guid id) { var post = await _postRepository.GetAsync(id); return ObjectMapper.Map(post); } public async Task CreatePostAsync(CreateForumPostDto input) { var post = new ForumPost( GuidGenerator.Create(), input.TopicId, input.Content, CurrentUser.Id.Value, CurrentUser.Name, input.ParentPostId, input.TenantId ); await _postRepository.InsertAsync(post, autoSave: true); // 🔽 Update topic var topic = await _topicRepository.GetAsync(input.TopicId); topic.ReplyCount++; topic.LastPostId = post.Id; topic.LastPostDate = post.CreationTime; topic.LastPostUserId = post.AuthorId; topic.LastPostUserName = post.AuthorName; await _topicRepository.UpdateAsync(topic); // 🔽 Update category var category = await _categoryRepository.GetAsync(topic.CategoryId); category.PostCount++; category.LastPostId = post.Id; category.LastPostDate = post.CreationTime; category.LastPostUserId = post.AuthorId; category.LastPostUserName = post.AuthorName; await _categoryRepository.UpdateAsync(category); return ObjectMapper.Map(post); } public async Task UpdatePostAsync(Guid id, UpdateForumPostDto input) { var post = await _postRepository.GetAsync(id); // Check if user can edit this post if (post.AuthorId != CurrentUser.Id && !await AuthorizationService.IsGrantedAsync("Forum.Posts.Edit")) { throw new AbpAuthorizationException(); } post.Content = input.Content; post.IsAcceptedAnswer = input.IsAcceptedAnswer; await _postRepository.UpdateAsync(post); return ObjectMapper.Map(post); } public async Task DeletePostAsync(Guid id) { var post = await _postRepository.GetAsync(id); var topic = await _topicRepository.GetAsync(post.TopicId); var category = await _categoryRepository.GetAsync(topic.CategoryId); await _postRepository.DeleteAsync(id); topic.ReplyCount = Math.Max(0, topic.ReplyCount - 1); category.PostCount = Math.Max(0, category.PostCount ?? 0 - 1); // 🔁 Last post değişti mi kontrol et var latestPost = await _postRepository .GetQueryableAsync() .ContinueWith(q => q.Result .Where(p => p.TopicId == topic.Id) .OrderByDescending(p => p.CreationTime) .FirstOrDefault() ); if (latestPost != null) { topic.LastPostId = latestPost.Id; topic.LastPostDate = latestPost.CreationTime; topic.LastPostUserId = latestPost.AuthorId; topic.LastPostUserName = latestPost.AuthorName; category.LastPostId = latestPost.Id; category.LastPostDate = latestPost.CreationTime; category.LastPostUserId = latestPost.AuthorId; category.LastPostUserName = latestPost.AuthorName; } else { // Tüm postlar silindiyse topic.LastPostId = null; topic.LastPostDate = null; topic.LastPostUserId = null; topic.LastPostUserName = null; category.LastPostId = null; category.LastPostDate = null; category.LastPostUserId = null; category.LastPostUserName = null; } await _topicRepository.UpdateAsync(topic); await _categoryRepository.UpdateAsync(category); } public async Task LikePostAsync(Guid id) { var post = await _postRepository.GetAsync(id); post.LikeCount++; await _postRepository.UpdateAsync(post); var topic = await _topicRepository.GetAsync(post.TopicId); var postsInTopic = await _postRepository.GetListAsync(p => p.TopicId == topic.Id); topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0); await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(post); } public async Task UnlikePostAsync(Guid id) { var post = await _postRepository.GetAsync(id); post.LikeCount = Math.Max(0, post.LikeCount ?? 0 - 1); await _postRepository.UpdateAsync(post); // 🔽 Topic'in toplam beğeni sayısını güncelle var topic = await _topicRepository.GetAsync(post.TopicId); var postsInTopic = await _postRepository.GetListAsync(p => p.TopicId == topic.Id); topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0); await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(post); } public async Task MarkPostAsync(Guid id) { var post = await _postRepository.GetAsync(id); post.IsAcceptedAnswer = true; await _postRepository.UpdateAsync(post); return ObjectMapper.Map(post); } public async Task UnmarkPostAsync(Guid id) { var post = await _postRepository.GetAsync(id); post.IsAcceptedAnswer = false; await _postRepository.UpdateAsync(post); return ObjectMapper.Map(post); } // Like/Unlike topic public async Task LikeTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.LikeCount++; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task UnlikeTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.LikeCount = Math.Max(0, topic.LikeCount - 1); await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task PinTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsPinned = true; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task UnpinTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsPinned = false; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task LockTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsLocked = true; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task UnlockTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsLocked = false; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task SolvedTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsSolved = true; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } public async Task UnsolvedTopicAsync(Guid id) { var topic = await _topicRepository.GetAsync(id); topic.IsSolved = false; await _topicRepository.UpdateAsync(topic); return ObjectMapper.Map(topic); } // Statistics public async Task GetForumStatsAsync() { var totalCategories = await _categoryRepository.CountAsync(); var totalTopics = await _topicRepository.CountAsync(); var totalPosts = await _postRepository.CountAsync(); var totalUsers = await _identityUserRepository.GetCountAsync(); return new ForumStatsDto { TotalCategories = totalCategories, TotalTopics = totalTopics, TotalPosts = totalPosts, TotalUsers = totalUsers, ActiveUsers = totalUsers }; } }