sozsoft-platform/api/src/Sozsoft.Platform.Application/Forum/ForumAppService.cs

692 lines
24 KiB
C#
Raw Normal View History

2026-02-24 20:44:16 +00:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
2026-07-10 09:22:54 +00:00
using Microsoft.EntityFrameworkCore;
2026-02-24 20:44:16 +00:00
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
namespace Sozsoft.Platform.Forum;
[Authorize]
public class ForumAppService : PlatformAppService, IForumAppService
{
private readonly IRepository<ForumCategory, Guid> _categoryRepository;
private readonly IRepository<ForumTopic, Guid> _topicRepository;
private readonly IRepository<ForumPost, Guid> _postRepository;
private readonly IIdentityUserRepository _identityUserRepository;
public ForumAppService(
IRepository<ForumCategory, Guid> categoryRepository,
IRepository<ForumTopic, Guid> topicRepository,
IRepository<ForumPost, Guid> postRepository,
IIdentityUserRepository identityUserRepository)
{
_categoryRepository = categoryRepository;
_topicRepository = topicRepository;
_postRepository = postRepository;
_identityUserRepository = identityUserRepository;
}
// Search functionality
public async Task<ForumSearchResultDto> SearchAsync(SearchForumInput input)
{
var result = new ForumSearchResultDto
{
Categories = [],
Topics = [],
Posts = [],
TotalCount = 0
};
if (string.IsNullOrWhiteSpace(input.Query))
return result;
2026-07-11 20:35:32 +00:00
var query = input.Query.Trim();
2026-02-24 20:44:16 +00:00
// Search in categories
if (input.SearchInCategories)
{
var categoryQuery = await _categoryRepository.GetQueryableAsync();
var categories = await AsyncExecuter.ToListAsync(
categoryQuery.Where(c => c.IsActive &&
2026-07-11 20:35:32 +00:00
(c.Name.Contains(query) ||
c.Description.Contains(query)))
2026-02-24 20:44:16 +00:00
.Take(10)
);
result.Categories = ObjectMapper.Map<List<ForumCategory>, List<ForumCategoryDto>>(categories);
}
// Search in topics
if (input.SearchInTopics)
{
var topicQuery = await _topicRepository.GetQueryableAsync();
var topics = await AsyncExecuter.ToListAsync(
topicQuery.Where(t =>
2026-07-11 20:35:32 +00:00
t.Title.Contains(query) ||
t.Content.Contains(query) ||
t.AuthorName.Contains(query))
2026-02-24 20:44:16 +00:00
.OrderByDescending(t => t.CreationTime)
.Take(20)
);
result.Topics = ObjectMapper.Map<List<ForumTopic>, List<ForumTopicDto>>(topics);
}
// Search in posts
if (input.SearchInPosts)
{
var postQuery = await _postRepository.GetQueryableAsync();
var posts = await AsyncExecuter.ToListAsync(
postQuery.Where(p =>
2026-07-11 20:35:32 +00:00
p.Content.Contains(query) ||
p.AuthorName.Contains(query))
2026-02-24 20:44:16 +00:00
.OrderByDescending(p => p.CreationTime)
.Take(30)
);
result.Posts = ObjectMapper.Map<List<ForumPost>, List<ForumPostDto>>(posts);
}
result.TotalCount = result.Categories.Count + result.Topics.Count + result.Posts.Count;
return result;
}
// Category management
public async Task<PagedResultDto<ForumCategoryDto>> 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))
{
2026-07-11 20:35:32 +00:00
var search = input.Search.Trim();
2026-02-24 20:44:16 +00:00
queryable = queryable.Where(c =>
2026-07-11 20:35:32 +00:00
c.Name.Contains(search) ||
c.Description.Contains(search));
2026-02-24 20:44:16 +00:00
}
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(
2026-07-11 20:35:32 +00:00
queryable.Skip(skipCount).Take(maxResultCount)
2026-02-24 20:44:16 +00:00
);
2026-07-11 20:35:32 +00:00
var categoryDtos = ObjectMapper.Map<List<ForumCategory>, List<ForumCategoryDto>>(categories);
var categoryIds = categories.Select(category => category.Id).ToList();
if (categoryIds.Count > 0)
{
var topicQuery = await _topicRepository.GetQueryableAsync();
var postQuery = await _postRepository.GetQueryableAsync();
var topicCountItems = await AsyncExecuter.ToListAsync(
topicQuery.Where(topic => categoryIds.Contains(topic.CategoryId))
.GroupBy(topic => topic.CategoryId)
.Select(group => new { CategoryId = group.Key, Count = group.Count() }));
var topicCounts = topicCountItems.ToDictionary(item => item.CategoryId, item => item.Count);
var postCountItems = await AsyncExecuter.ToListAsync(
postQuery.Where(post => categoryIds.Contains(post.Topic.CategoryId))
.GroupBy(post => post.Topic.CategoryId)
.Select(group => new { CategoryId = group.Key, Count = group.Count() }));
var postCounts = postCountItems.ToDictionary(item => item.CategoryId, item => item.Count);
foreach (var category in categoryDtos)
{
category.TopicCount = topicCounts.GetValueOrDefault(category.Id);
category.PostCount = postCounts.GetValueOrDefault(category.Id);
}
}
return new PagedResultDto<ForumCategoryDto>(totalCount, categoryDtos);
2026-02-24 20:44:16 +00:00
}
public async Task<ForumCategoryDto> GetCategoryAsync(Guid id)
{
var category = await _categoryRepository.GetAsync(id);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
public async Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug)
{
var category = await _categoryRepository.FirstOrDefaultAsync(c => c.Slug == slug);
if (category == null)
throw new EntityNotFoundException(typeof(ForumCategory), slug);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Create")]
public async Task<ForumCategoryDto> 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, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Update")]
public async Task<ForumCategoryDto> 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, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Update")]
public async Task<ForumCategoryDto> UpdateCategoryLockAsync(Guid id)
{
var category = await _categoryRepository.GetAsync(id);
category.IsLocked = !category.IsLocked;
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Update")]
public async Task<ForumCategoryDto> UpdateCategoryActiveAsync(Guid id)
{
var category = await _categoryRepository.GetAsync(id);
category.IsActive = !category.IsActive;
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Delete")]
[UnitOfWork]
public async Task DeleteCategoryAsync(Guid id)
{
// Tüm topic ID'lerini al
var topicQueryable = await _topicRepository.GetQueryableAsync();
var topicIds = await AsyncExecuter.ToListAsync(
topicQueryable
.Where(t => t.CategoryId == id)
.Select(t => t.Id)
);
if (topicIds.Any())
{
// Bulk delete - daha performanslı
await _postRepository.DeleteAsync(p => topicIds.Contains(p.TopicId));
await _topicRepository.DeleteAsync(t => t.CategoryId == id);
}
await _categoryRepository.DeleteAsync(id);
}
// Topic management
public async Task<PagedResultDto<ForumTopicDto>> 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))
{
2026-07-11 20:35:32 +00:00
var search = input.Search.Trim();
2026-02-24 20:44:16 +00:00
queryable = queryable.Where(t =>
2026-07-11 20:35:32 +00:00
t.Title.Contains(search) ||
t.Content.Contains(search));
2026-02-24 20:44:16 +00:00
}
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)
);
2026-07-11 20:35:32 +00:00
var topicDtos = ObjectMapper.Map<List<ForumTopic>, List<ForumTopicDto>>(topics);
var topicIds = topics.Select(topic => topic.Id).ToList();
if (topicIds.Count > 0)
{
var postQuery = await _postRepository.GetQueryableAsync();
var replyCountItems = await AsyncExecuter.ToListAsync(
postQuery.Where(post => topicIds.Contains(post.TopicId))
.GroupBy(post => post.TopicId)
.Select(group => new { TopicId = group.Key, Count = group.Count() }));
var replyCounts = replyCountItems.ToDictionary(item => item.TopicId, item => item.Count);
foreach (var topic in topicDtos)
{
topic.ReplyCount = replyCounts.GetValueOrDefault(topic.Id);
}
}
return new PagedResultDto<ForumTopicDto>(totalCount, topicDtos);
2026-02-24 20:44:16 +00:00
}
public async Task<ForumTopicDto> GetTopicAsync(Guid id)
{
2026-07-10 09:22:54 +00:00
var queryable = await _topicRepository.GetQueryableAsync();
var affectedRows = await queryable
.Where(t => t.Id == id)
.ExecuteUpdateAsync(setters =>
setters.SetProperty(t => t.ViewCount, t => t.ViewCount + 1)
);
2026-02-24 20:44:16 +00:00
2026-07-10 09:22:54 +00:00
if (affectedRows == 0)
2026-02-24 20:44:16 +00:00
{
2026-07-10 09:22:54 +00:00
throw new EntityNotFoundException(typeof(ForumTopic), id);
}
var topic = await AsyncExecuter.FirstOrDefaultAsync(
queryable
.AsNoTracking()
.Where(t => t.Id == id)
);
if (topic == null)
{
throw new EntityNotFoundException(typeof(ForumTopic), id);
}
2026-02-24 20:44:16 +00:00
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
[UnitOfWork]
public async Task<ForumTopicDto> 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, autoSave: false);
// Update category topic count
var category = await _categoryRepository.GetAsync(input.CategoryId);
category.TopicCount++;
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> 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, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
[UnitOfWork]
public async Task DeleteTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
// Post sayısını al
var postCount = await _postRepository.CountAsync(p => p.TopicId == id);
// Tüm postları sil
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);
category.PostCount = Math.Max(0, (category.PostCount ?? 0) - postCount);
await _categoryRepository.UpdateAsync(category, autoSave: false);
await _topicRepository.DeleteAsync(id, autoSave: true);
}
// Post management
public async Task<PagedResultDto<ForumPostDto>> GetPostsAsync(GetPostsInput input)
{
var queryable = await _postRepository.GetQueryableAsync();
if (input.TopicId.HasValue)
{
queryable = queryable.Where(p => p.TopicId == input.TopicId.Value);
}
if (input.IsAcceptedAnswer.HasValue)
{
queryable = queryable.Where(p => p.IsAcceptedAnswer == input.IsAcceptedAnswer.Value);
}
if (!string.IsNullOrWhiteSpace(input.Search))
{
2026-07-11 20:35:32 +00:00
var search = input.Search.Trim();
queryable = queryable.Where(p => p.Content.Contains(search));
2026-02-24 20:44:16 +00:00
}
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<ForumPostDto>(
totalCount,
ObjectMapper.Map<List<ForumPost>, List<ForumPostDto>>(posts)
);
}
public async Task<ForumPostDto> GetPostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
[UnitOfWork]
public async Task<ForumPostDto> 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: false);
// 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, autoSave: false);
// 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, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
public async Task<ForumPostDto> 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, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(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);
2026-07-11 20:35:32 +00:00
category.PostCount = Math.Max(0, (category.PostCount ?? 0) - 1);
2026-02-24 20:44:16 +00:00
2026-07-10 09:22:54 +00:00
// Last post değişti mi kontrol et
var postsQueryable = await _postRepository.GetQueryableAsync();
var latestPost = await AsyncExecuter.FirstOrDefaultAsync(
postsQueryable
2026-02-24 20:44:16 +00:00
.Where(p => p.TopicId == topic.Id)
.OrderByDescending(p => p.CreationTime)
2026-07-10 09:22:54 +00:00
);
2026-02-24 20:44:16 +00:00
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);
}
[UnitOfWork]
public async Task<ForumPostDto> LikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.LikeCount++;
await _postRepository.UpdateAsync(post, autoSave: false);
var topic = await _topicRepository.GetAsync(post.TopicId);
// Topic'teki tüm postların toplam like'larını hesapla (optimizasyon)
var queryable = await _postRepository.GetQueryableAsync();
var totalLikes = await AsyncExecuter.SumAsync(
queryable.Where(p => p.TopicId == topic.Id),
p => p.LikeCount ?? 0
);
topic.LikeCount = totalLikes;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
[UnitOfWork]
public async Task<ForumPostDto> UnlikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.LikeCount = Math.Max(0, (post.LikeCount ?? 0) - 1);
await _postRepository.UpdateAsync(post, autoSave: false);
// Topic'in toplam beğeni sayısını güncelle (optimizasyon)
var topic = await _topicRepository.GetAsync(post.TopicId);
var queryable = await _postRepository.GetQueryableAsync();
var totalLikes = await AsyncExecuter.SumAsync(
queryable.Where(p => p.TopicId == topic.Id),
p => p.LikeCount ?? 0
);
topic.LikeCount = totalLikes;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
public async Task<ForumPostDto> MarkPostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = true;
await _postRepository.UpdateAsync(post, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
public async Task<ForumPostDto> UnmarkPostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = false;
await _postRepository.UpdateAsync(post, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
// Like/Unlike topic
public async Task<ForumTopicDto> LikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount++;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnlikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> PinTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = true;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnpinTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = false;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> LockTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = true;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnlockTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = false;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> SolvedTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = true;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnsolvedTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = false;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
// Statistics
public async Task<ForumStatsDto> 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
};
}
}