2025-11-11 19:49:52 +00:00
|
|
|
|
using System;
|
2025-06-23 14:58:13 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
2025-11-11 19:49:52 +00:00
|
|
|
|
namespace Erp.Platform.Forum;
|
2025-06-23 14:58:13 +00:00
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
|
|
|
|
|
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<ForumCategory>, List<ForumCategoryDto>>(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<ForumTopic>, List<ForumTopicDto>>(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<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))
|
|
|
|
|
|
{
|
|
|
|
|
|
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<ForumCategoryDto>(
|
|
|
|
|
|
totalCount,
|
|
|
|
|
|
ObjectMapper.Map<List<ForumCategory>, List<ForumCategoryDto>>(categories)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-10-07 06:28:16 +00:00
|
|
|
|
var category = new ForumCategory
|
2025-06-23 14:58:13 +00:00
|
|
|
|
{
|
2025-10-07 06:28:16 +00:00
|
|
|
|
Name = input.Name,
|
|
|
|
|
|
Slug = input.Slug,
|
|
|
|
|
|
Description = input.Description,
|
|
|
|
|
|
Icon = input.Icon,
|
|
|
|
|
|
DisplayOrder = input.DisplayOrder,
|
2025-06-23 14:58:13 +00:00
|
|
|
|
IsActive = input.IsActive,
|
|
|
|
|
|
IsLocked = input.IsLocked
|
2025-10-07 06:28:16 +00:00
|
|
|
|
|
2025-06-23 14:58:13 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await _categoryRepository.InsertAsync(category);
|
|
|
|
|
|
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);
|
|
|
|
|
|
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 21:22:11 +00:00
|
|
|
|
[Authorize("App.ForumManagement.Update")]
|
|
|
|
|
|
public async Task<ForumCategoryDto> UpdateCategoryLockAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var category = await _categoryRepository.GetAsync(id);
|
|
|
|
|
|
category.IsLocked = !category.IsLocked;
|
2025-06-24 07:09:33 +00:00
|
|
|
|
|
2025-06-23 21:22:11 +00:00
|
|
|
|
await _categoryRepository.UpdateAsync(category);
|
|
|
|
|
|
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 07:09:33 +00:00
|
|
|
|
[Authorize("App.ForumManagement.Update")]
|
2025-06-23 21:22:11 +00:00
|
|
|
|
public async Task<ForumCategoryDto> UpdateCategoryActiveAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var category = await _categoryRepository.GetAsync(id);
|
|
|
|
|
|
category.IsActive = !category.IsActive;
|
2025-06-24 07:09:33 +00:00
|
|
|
|
|
2025-06-23 21:22:11 +00:00
|
|
|
|
await _categoryRepository.UpdateAsync(category);
|
|
|
|
|
|
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 14:58:13 +00:00
|
|
|
|
[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<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))
|
|
|
|
|
|
{
|
|
|
|
|
|
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<ForumTopicDto>(
|
|
|
|
|
|
totalCount,
|
|
|
|
|
|
ObjectMapper.Map<List<ForumTopic>, List<ForumTopicDto>>(topics)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ForumTopicDto> GetTopicAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var topic = await _topicRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
|
|
topic.ViewCount++;
|
|
|
|
|
|
await _topicRepository.UpdateAsync(topic);
|
|
|
|
|
|
|
|
|
|
|
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ForumTopicDto> CreateTopicAsync(CreateForumTopicDto input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var topic = new ForumTopic(
|
|
|
|
|
|
GuidGenerator.Create(),
|
|
|
|
|
|
input.Title,
|
|
|
|
|
|
input.Content,
|
|
|
|
|
|
input.CategoryId,
|
|
|
|
|
|
CurrentUser.Id.Value,
|
2025-06-24 07:09:33 +00:00
|
|
|
|
CurrentUser.Name,
|
|
|
|
|
|
input.TenantId
|
2025-06-23 14:58:13 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
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<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);
|
|
|
|
|
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(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);
|
2025-08-20 19:26:08 +00:00
|
|
|
|
category.TopicCount = Math.Max(0, category.TopicCount ?? 0 - 1);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
var postCount = await _postRepository.CountAsync(p => p.TopicId == id);
|
2025-08-20 19:26:08 +00:00
|
|
|
|
category.PostCount = Math.Max(0, category.PostCount ?? 0 - postCount);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
await _categoryRepository.UpdateAsync(category);
|
|
|
|
|
|
|
|
|
|
|
|
await _topicRepository.DeleteAsync(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
|
|
// 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<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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ForumPostDto> CreatePostAsync(CreateForumPostDto input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var post = new ForumPost(
|
|
|
|
|
|
GuidGenerator.Create(),
|
|
|
|
|
|
input.TopicId,
|
|
|
|
|
|
input.Content,
|
|
|
|
|
|
CurrentUser.Id.Value,
|
|
|
|
|
|
CurrentUser.Name,
|
2025-06-24 07:09:33 +00:00
|
|
|
|
input.ParentPostId,
|
|
|
|
|
|
input.TenantId
|
2025-06-23 14:58:13 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await _postRepository.InsertAsync(post, autoSave: true);
|
|
|
|
|
|
|
2025-11-11 19:49:52 +00:00
|
|
|
|
// 🔽 Update topic
|
2025-06-23 14:58:13 +00:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-11-11 19:49:52 +00:00
|
|
|
|
// 🔽 Update category
|
2025-06-23 14:58:13 +00:00
|
|
|
|
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<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);
|
|
|
|
|
|
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);
|
2025-08-20 19:26:08 +00:00
|
|
|
|
category.PostCount = Math.Max(0, category.PostCount ?? 0 - 1);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
|
2025-11-12 12:59:31 +00:00
|
|
|
|
// 🔠Last post değişti mi kontrol et
|
2025-06-23 14:58:13 +00:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2025-11-12 12:59:31 +00:00
|
|
|
|
// Tüm postlar silindiyse
|
2025-06-23 14:58:13 +00:00
|
|
|
|
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<ForumPostDto> 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);
|
|
|
|
|
|
|
2025-08-20 19:26:08 +00:00
|
|
|
|
topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
await _topicRepository.UpdateAsync(topic);
|
|
|
|
|
|
|
|
|
|
|
|
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ForumPostDto> UnlikePostAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var post = await _postRepository.GetAsync(id);
|
2025-08-20 19:26:08 +00:00
|
|
|
|
post.LikeCount = Math.Max(0, post.LikeCount ?? 0 - 1);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
await _postRepository.UpdateAsync(post);
|
|
|
|
|
|
|
2025-11-12 12:59:31 +00:00
|
|
|
|
// 🔽 Topic'in toplam beğeni sayısını güncelle
|
2025-06-23 14:58:13 +00:00
|
|
|
|
var topic = await _topicRepository.GetAsync(post.TopicId);
|
|
|
|
|
|
|
|
|
|
|
|
var postsInTopic = await _postRepository.GetListAsync(p => p.TopicId == topic.Id);
|
2025-08-20 19:26:08 +00:00
|
|
|
|
topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0);
|
2025-06-23 14:58:13 +00:00
|
|
|
|
|
|
|
|
|
|
await _topicRepository.UpdateAsync(topic);
|
|
|
|
|
|
|
|
|
|
|
|
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 07:09:33 +00:00
|
|
|
|
public async Task<ForumPostDto> MarkPostAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var post = await _postRepository.GetAsync(id);
|
|
|
|
|
|
post.IsAcceptedAnswer = true;
|
|
|
|
|
|
|
|
|
|
|
|
await _postRepository.UpdateAsync(post);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
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);
|
|
|
|
|
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 14:58:13 +00:00
|
|
|
|
// 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,
|
2025-11-14 20:59:46 +00:00
|
|
|
|
ActiveUsers = totalUsers
|
2025-06-23 14:58:13 +00:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-11 19:49:52 +00:00
|
|
|
|
|