Blog Application Service güncellemeleri
This commit is contained in:
parent
7386ff91fa
commit
d891ae3f85
9 changed files with 426 additions and 340 deletions
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
|
namespace Kurs.Platform.Blog;
|
||||||
|
|
||||||
|
public class BlogPostAndCategoriesDto
|
||||||
|
{
|
||||||
|
public PagedResultDto<BlogPostListDto> Posts { get; set; }
|
||||||
|
public List<BlogCategoryDto> Categories { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Kurs.Platform.Blog;
|
||||||
public interface IBlogAppService : IApplicationService
|
public interface IBlogAppService : IApplicationService
|
||||||
{
|
{
|
||||||
// Blog Post methods
|
// Blog Post methods
|
||||||
Task<PagedResultDto<BlogPostListDto>> GetPostsAsync(GetBlogPostsInput input);
|
Task<BlogPostAndCategoriesDto> GetPostListAsync(GetBlogPostsInput input);
|
||||||
Task<BlogPostDto> GetPostAsync(Guid id);
|
Task<BlogPostDto> GetPostAsync(Guid id);
|
||||||
Task<BlogPostDto> GetPostBySlugAsync(string slug);
|
Task<BlogPostDto> GetPostBySlugAsync(string slug);
|
||||||
Task<BlogPostDto> CreatePostAsync(CreateUpdateBlogPostDto input);
|
Task<BlogPostDto> CreatePostAsync(CreateUpdateBlogPostDto input);
|
||||||
|
|
@ -19,7 +19,7 @@ public interface IBlogAppService : IApplicationService
|
||||||
Task<BlogPostDto> UnpublishPostAsync(Guid id);
|
Task<BlogPostDto> UnpublishPostAsync(Guid id);
|
||||||
|
|
||||||
// Blog Category methods
|
// Blog Category methods
|
||||||
Task<List<BlogCategoryDto>> GetCategoriesAsync();
|
// Task<List<BlogCategoryDto>> GetCategoriesAsync();
|
||||||
Task<BlogCategoryDto> GetCategoryAsync(Guid id);
|
Task<BlogCategoryDto> GetCategoryAsync(Guid id);
|
||||||
Task<BlogCategoryDto> CreateCategoryAsync(CreateUpdateBlogCategoryDto input);
|
Task<BlogCategoryDto> CreateCategoryAsync(CreateUpdateBlogCategoryDto input);
|
||||||
Task<BlogCategoryDto> UpdateCategoryAsync(Guid id, CreateUpdateBlogCategoryDto input);
|
Task<BlogCategoryDto> UpdateCategoryAsync(Guid id, CreateUpdateBlogCategoryDto input);
|
||||||
|
|
|
||||||
|
|
@ -11,302 +11,370 @@ using Volo.Abp.Domain.Entities;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
|
|
||||||
namespace Kurs.Platform.Blog
|
namespace Kurs.Platform.Blog;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public class BlogAppService : PlatformAppService, IBlogAppService
|
||||||
{
|
{
|
||||||
[Authorize]
|
private readonly IRepository<BlogPost, Guid> _postRepository;
|
||||||
public class BlogAppService : PlatformAppService, IBlogAppService
|
private readonly IRepository<BlogCategory, Guid> _categoryRepository;
|
||||||
|
private readonly ICurrentUser _currentUser;
|
||||||
|
private readonly IStringLocalizer<PlatformResource> _localizer;
|
||||||
|
|
||||||
|
public BlogAppService(
|
||||||
|
IRepository<BlogPost, Guid> postRepository,
|
||||||
|
IRepository<BlogCategory, Guid> categoryRepository,
|
||||||
|
ICurrentUser currentUser,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
{
|
{
|
||||||
private readonly IRepository<BlogPost, Guid> _postRepository;
|
_postRepository = postRepository;
|
||||||
private readonly IRepository<BlogCategory, Guid> _categoryRepository;
|
_categoryRepository = categoryRepository;
|
||||||
private readonly ICurrentUser _currentUser;
|
_currentUser = currentUser;
|
||||||
private readonly IStringLocalizer<PlatformResource> _localizer;
|
_localizer = localizer;
|
||||||
|
|
||||||
public BlogAppService(
|
|
||||||
IRepository<BlogPost, Guid> postRepository,
|
|
||||||
IRepository<BlogCategory, Guid> categoryRepository,
|
|
||||||
ICurrentUser currentUser,
|
|
||||||
IStringLocalizer<PlatformResource> localizer)
|
|
||||||
{
|
|
||||||
_postRepository = postRepository;
|
|
||||||
_categoryRepository = categoryRepository;
|
|
||||||
_currentUser = currentUser;
|
|
||||||
_localizer = localizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blog Post methods
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<PagedResultDto<BlogPostListDto>> GetPostsAsync(GetBlogPostsInput input)
|
|
||||||
{
|
|
||||||
var allPosts = await _postRepository.GetListAsync(); // Tüm kayıtlar memory'ye alınır
|
|
||||||
|
|
||||||
var filtered = allPosts.Where(post =>
|
|
||||||
{
|
|
||||||
var searchMatch = string.IsNullOrWhiteSpace(input.Search) ||
|
|
||||||
post.ContentTr.Contains(input.Search, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
post.ContentEn.Contains(input.Search, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var categoryMatch = !input.CategoryId.HasValue || post.CategoryId == input.CategoryId.Value;
|
|
||||||
|
|
||||||
return searchMatch && categoryMatch;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var totalCount = filtered.Count;
|
|
||||||
|
|
||||||
var pagedPosts = filtered
|
|
||||||
.OrderByDescending(x => x.CreationTime)
|
|
||||||
.Skip(input.SkipCount)
|
|
||||||
.Take(input.MaxResultCount)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList();
|
|
||||||
var categories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id));
|
|
||||||
var categoryDict = categories.ToDictionary(x => x.Id, x => x);
|
|
||||||
|
|
||||||
var postIds = pagedPosts.Select(x => x.Id).ToList();
|
|
||||||
|
|
||||||
var postDtos = pagedPosts.Select(post =>
|
|
||||||
{
|
|
||||||
var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post);
|
|
||||||
|
|
||||||
if (categoryDict.TryGetValue(post.CategoryId, out var category))
|
|
||||||
{
|
|
||||||
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.Author = new AuthorDto
|
|
||||||
{
|
|
||||||
Id = post.AuthorId,
|
|
||||||
Name = "User"
|
|
||||||
};
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return new PagedResultDto<BlogPostListDto>(totalCount, postDtos);
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<BlogPostDto> GetPostAsync(Guid id)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.GetAsync(id);
|
|
||||||
var dto = ObjectMapper.Map<BlogPost, BlogPostDto>(post);
|
|
||||||
|
|
||||||
// Get category
|
|
||||||
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(
|
|
||||||
await _categoryRepository.GetAsync(post.CategoryId)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get author info
|
|
||||||
dto.Author = new AuthorDto
|
|
||||||
{
|
|
||||||
Id = post.AuthorId,
|
|
||||||
Name = "User"
|
|
||||||
};
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.FirstOrDefaultAsync(x => x.Slug == slug);
|
|
||||||
if (post == null)
|
|
||||||
{
|
|
||||||
throw new EntityNotFoundException(typeof(BlogPost));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await GetPostAsync(post.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPostDto> CreatePostAsync(CreateUpdateBlogPostDto input)
|
|
||||||
{
|
|
||||||
var post = new BlogPost(
|
|
||||||
GuidGenerator.Create(),
|
|
||||||
input.Title,
|
|
||||||
input.Slug,
|
|
||||||
input.ContentTr,
|
|
||||||
input.ContentEn,
|
|
||||||
input.Summary,
|
|
||||||
input.ReadTime,
|
|
||||||
input.CoverImage,
|
|
||||||
input.CategoryId,
|
|
||||||
_currentUser.Id.Value,
|
|
||||||
true,
|
|
||||||
DateTime.UtcNow,
|
|
||||||
(Guid?)CurrentTenant.Id
|
|
||||||
);
|
|
||||||
|
|
||||||
await _postRepository.InsertAsync(post, autoSave: true);
|
|
||||||
|
|
||||||
return await GetPostAsync(post.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPostDto> UpdatePostAsync(Guid id, CreateUpdateBlogPostDto input)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.GetAsync(id);
|
|
||||||
|
|
||||||
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Update"))
|
|
||||||
{
|
|
||||||
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
post.Title = input.Title;
|
|
||||||
post.Slug = input.Slug;
|
|
||||||
post.Summary = input.Summary;
|
|
||||||
post.CoverImage = input.CoverImage;
|
|
||||||
post.ContentTr = input.ContentTr;
|
|
||||||
post.ContentEn = input.ContentEn;
|
|
||||||
|
|
||||||
if (input.IsPublished) post.Publish(); else post.Unpublish();
|
|
||||||
|
|
||||||
if (post.CategoryId != input.CategoryId)
|
|
||||||
{
|
|
||||||
var oldCategory = await _categoryRepository.GetAsync(post.CategoryId);
|
|
||||||
oldCategory.DecrementPostCount();
|
|
||||||
await _categoryRepository.UpdateAsync(oldCategory);
|
|
||||||
|
|
||||||
var newCategory = await _categoryRepository.GetAsync(input.CategoryId);
|
|
||||||
newCategory.IncrementPostCount();
|
|
||||||
await _categoryRepository.UpdateAsync(newCategory);
|
|
||||||
|
|
||||||
post.CategoryId = input.CategoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _postRepository.UpdateAsync(post);
|
|
||||||
|
|
||||||
return await GetPostAsync(post.Id); // ✅ DTO dönülüyor
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeletePostAsync(Guid id)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.GetAsync(id);
|
|
||||||
|
|
||||||
// Check if user is author or has permission
|
|
||||||
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Delete"))
|
|
||||||
{
|
|
||||||
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update category post count
|
|
||||||
var category = await _categoryRepository.GetAsync(post.CategoryId);
|
|
||||||
category.DecrementPostCount();
|
|
||||||
await _categoryRepository.UpdateAsync(category);
|
|
||||||
|
|
||||||
await _postRepository.DeleteAsync(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPostDto> PublishPostAsync(Guid id)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.GetAsync(id);
|
|
||||||
|
|
||||||
// Check if user is author or has permission
|
|
||||||
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Publish"))
|
|
||||||
{
|
|
||||||
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
post.Publish();
|
|
||||||
await _postRepository.UpdateAsync(post);
|
|
||||||
|
|
||||||
return await GetPostAsync(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPostDto> UnpublishPostAsync(Guid id)
|
|
||||||
{
|
|
||||||
var post = await _postRepository.GetAsync(id);
|
|
||||||
|
|
||||||
// Check if user is author or has permission
|
|
||||||
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Publish"))
|
|
||||||
{
|
|
||||||
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
post.Unpublish();
|
|
||||||
await _postRepository.UpdateAsync(post);
|
|
||||||
|
|
||||||
return await GetPostAsync(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blog Category methods
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<List<BlogCategoryDto>> GetCategoriesAsync()
|
|
||||||
{
|
|
||||||
var categories = await _categoryRepository.GetListAsync();
|
|
||||||
|
|
||||||
var postQuery = await _postRepository.GetQueryableAsync();
|
|
||||||
|
|
||||||
var groupedCounts = postQuery
|
|
||||||
.Where(p => p.IsPublished) // sadece yayınlanmış yazılar
|
|
||||||
.GroupBy(p => p.CategoryId)
|
|
||||||
.Select(g => new { CategoryId = g.Key, Count = g.Count() })
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var dtoList = ObjectMapper.Map<List<BlogCategory>, List<BlogCategoryDto>>(categories);
|
|
||||||
|
|
||||||
foreach (var dto in dtoList)
|
|
||||||
{
|
|
||||||
dto.PostCount = groupedCounts
|
|
||||||
.FirstOrDefault(x => x.CategoryId == dto.Id)?.Count ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dtoList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogCategoryDto> GetCategoryAsync(Guid id)
|
|
||||||
{
|
|
||||||
var category = await _categoryRepository.GetAsync(id);
|
|
||||||
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize("App.BlogManagement.Create")]
|
|
||||||
public async Task<BlogCategoryDto> CreateCategoryAsync(CreateUpdateBlogCategoryDto input)
|
|
||||||
{
|
|
||||||
var category = new BlogCategory(
|
|
||||||
GuidGenerator.Create(),
|
|
||||||
input.Name,
|
|
||||||
input.Slug,
|
|
||||||
input.Description,
|
|
||||||
CurrentTenant.Id
|
|
||||||
);
|
|
||||||
|
|
||||||
category.Icon = input.Icon;
|
|
||||||
category.DisplayOrder = input.DisplayOrder;
|
|
||||||
category.IsActive = input.IsActive;
|
|
||||||
|
|
||||||
await _categoryRepository.InsertAsync(category);
|
|
||||||
|
|
||||||
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize("App.BlogManagement.Update")]
|
|
||||||
public async Task<BlogCategoryDto> UpdateCategoryAsync(Guid id, CreateUpdateBlogCategoryDto 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;
|
|
||||||
|
|
||||||
await _categoryRepository.UpdateAsync(category);
|
|
||||||
|
|
||||||
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize("App.BlogManagement.Delete")]
|
|
||||||
public async Task DeleteCategoryAsync(Guid id)
|
|
||||||
{
|
|
||||||
// Check if category has posts
|
|
||||||
var hasPost = await _postRepository.AnyAsync(x => x.CategoryId == id);
|
|
||||||
if (hasPost)
|
|
||||||
{
|
|
||||||
throw new Volo.Abp.BusinessException("Cannot delete category with posts");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _categoryRepository.DeleteAsync(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blog Post methods
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<BlogPostAndCategoriesDto> GetPostListAsync(GetBlogPostsInput input)
|
||||||
|
{
|
||||||
|
// IQueryable
|
||||||
|
var postQuery = await _postRepository.GetQueryableAsync();
|
||||||
|
|
||||||
|
// 🔎 Arama
|
||||||
|
if (!input.Search.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
postQuery = postQuery.Where(p =>
|
||||||
|
p.ContentTr.Contains(input.Search) ||
|
||||||
|
p.ContentEn.Contains(input.Search));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📁 Kategori filtresi
|
||||||
|
if (input.CategoryId.HasValue)
|
||||||
|
{
|
||||||
|
postQuery = postQuery.Where(p => p.CategoryId == input.CategoryId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toplam adet (sayfalama öncesi)
|
||||||
|
var totalCount = await AsyncExecuter.CountAsync(postQuery);
|
||||||
|
|
||||||
|
// Sayfalama + sıralama
|
||||||
|
var pagedPosts = await AsyncExecuter.ToListAsync(
|
||||||
|
postQuery
|
||||||
|
.OrderByDescending(p => p.CreationTime)
|
||||||
|
.PageBy(input)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sayfadaki kategori kayıtları
|
||||||
|
var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList();
|
||||||
|
var pageCategories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id));
|
||||||
|
var categoryDict = pageCategories.ToDictionary(x => x.Id, x => x);
|
||||||
|
|
||||||
|
// Post DTO mapping
|
||||||
|
var postDtos = pagedPosts.Select(post =>
|
||||||
|
{
|
||||||
|
var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post);
|
||||||
|
if (categoryDict.TryGetValue(post.CategoryId, out var c))
|
||||||
|
{
|
||||||
|
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
dto.Author = new AuthorDto { Id = post.AuthorId, Name = "User" };
|
||||||
|
return dto;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
// ----------- KATEGORİLER (PostCount ile) -----------
|
||||||
|
var allCategories = await _categoryRepository.GetListAsync();
|
||||||
|
|
||||||
|
var allPostQuery = await _postRepository.GetQueryableAsync();
|
||||||
|
var counts = await AsyncExecuter.ToListAsync(
|
||||||
|
allPostQuery
|
||||||
|
.Where(p => p.IsPublished)
|
||||||
|
.GroupBy(p => p.CategoryId)
|
||||||
|
.Select(g => new { g.Key, Count = g.Count() })
|
||||||
|
);
|
||||||
|
|
||||||
|
var countDict = counts.ToDictionary(x => x.Key, x => x.Count);
|
||||||
|
|
||||||
|
var categoryDtos = ObjectMapper.Map<List<BlogCategory>, List<BlogCategoryDto>>(allCategories);
|
||||||
|
foreach (var dto in categoryDtos)
|
||||||
|
{
|
||||||
|
dto.PostCount = countDict.GetOrDefault(dto.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BlogPostAndCategoriesDto
|
||||||
|
{
|
||||||
|
Posts = new PagedResultDto<BlogPostListDto>(totalCount, postDtos),
|
||||||
|
Categories = categoryDtos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async Task<PagedResultDto<BlogPostListDto>> GetPostsAsync(GetBlogPostsInput input)
|
||||||
|
// {
|
||||||
|
// var allPosts = await _postRepository.GetListAsync(); // Tüm kayıtlar memory'ye alınır
|
||||||
|
// var filtered = allPosts.Where(post =>
|
||||||
|
// {
|
||||||
|
// var searchMatch = string.IsNullOrWhiteSpace(input.Search) ||
|
||||||
|
// post.ContentTr.Contains(input.Search, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
// post.ContentEn.Contains(input.Search, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// var categoryMatch = !input.CategoryId.HasValue || post.CategoryId == input.CategoryId.Value;
|
||||||
|
|
||||||
|
// return searchMatch && categoryMatch;
|
||||||
|
// }).ToList();
|
||||||
|
|
||||||
|
// var totalCount = filtered.Count;
|
||||||
|
// var pagedPosts = filtered
|
||||||
|
// .OrderByDescending(x => x.CreationTime)
|
||||||
|
// .Skip(input.SkipCount)
|
||||||
|
// .Take(input.MaxResultCount)
|
||||||
|
// .ToList();
|
||||||
|
|
||||||
|
// var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList();
|
||||||
|
// var categories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id));
|
||||||
|
// var categoryDict = categories.ToDictionary(x => x.Id, x => x);
|
||||||
|
|
||||||
|
// var postIds = pagedPosts.Select(x => x.Id).ToList();
|
||||||
|
// var postDtos = pagedPosts.Select(post =>
|
||||||
|
// {
|
||||||
|
// var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post);
|
||||||
|
// if (categoryDict.TryGetValue(post.CategoryId, out var category))
|
||||||
|
// {
|
||||||
|
// dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// dto.Author = new AuthorDto
|
||||||
|
// {
|
||||||
|
// Id = post.AuthorId,
|
||||||
|
// Name = "User"
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return dto;
|
||||||
|
// }).ToList();
|
||||||
|
|
||||||
|
// return new PagedResultDto<BlogPostListDto>(totalCount, postDtos);
|
||||||
|
// }
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<BlogPostDto> GetPostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
var dto = ObjectMapper.Map<BlogPost, BlogPostDto>(post);
|
||||||
|
|
||||||
|
// Get category
|
||||||
|
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(
|
||||||
|
await _categoryRepository.GetAsync(post.CategoryId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get author info
|
||||||
|
dto.Author = new AuthorDto
|
||||||
|
{
|
||||||
|
Id = post.AuthorId,
|
||||||
|
Name = "User"
|
||||||
|
};
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.FirstOrDefaultAsync(x => x.Slug == slug);
|
||||||
|
if (post == null)
|
||||||
|
{
|
||||||
|
throw new EntityNotFoundException(typeof(BlogPost));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetPostAsync(post.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPostDto> CreatePostAsync(CreateUpdateBlogPostDto input)
|
||||||
|
{
|
||||||
|
var post = new BlogPost(
|
||||||
|
GuidGenerator.Create(),
|
||||||
|
input.Title,
|
||||||
|
input.Slug,
|
||||||
|
input.ContentTr,
|
||||||
|
input.ContentEn,
|
||||||
|
input.Summary,
|
||||||
|
input.ReadTime,
|
||||||
|
input.CoverImage,
|
||||||
|
input.CategoryId,
|
||||||
|
_currentUser.Id.Value,
|
||||||
|
true,
|
||||||
|
DateTime.UtcNow,
|
||||||
|
(Guid?)CurrentTenant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
await _postRepository.InsertAsync(post, autoSave: true);
|
||||||
|
|
||||||
|
return await GetPostAsync(post.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPostDto> UpdatePostAsync(Guid id, CreateUpdateBlogPostDto input)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
|
||||||
|
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Update"))
|
||||||
|
{
|
||||||
|
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Title = input.Title;
|
||||||
|
post.Slug = input.Slug;
|
||||||
|
post.Summary = input.Summary;
|
||||||
|
post.CoverImage = input.CoverImage;
|
||||||
|
post.ContentTr = input.ContentTr;
|
||||||
|
post.ContentEn = input.ContentEn;
|
||||||
|
|
||||||
|
if (input.IsPublished) post.Publish(); else post.Unpublish();
|
||||||
|
|
||||||
|
if (post.CategoryId != input.CategoryId)
|
||||||
|
{
|
||||||
|
var oldCategory = await _categoryRepository.GetAsync(post.CategoryId);
|
||||||
|
oldCategory.DecrementPostCount();
|
||||||
|
await _categoryRepository.UpdateAsync(oldCategory);
|
||||||
|
|
||||||
|
var newCategory = await _categoryRepository.GetAsync(input.CategoryId);
|
||||||
|
newCategory.IncrementPostCount();
|
||||||
|
await _categoryRepository.UpdateAsync(newCategory);
|
||||||
|
|
||||||
|
post.CategoryId = input.CategoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _postRepository.UpdateAsync(post);
|
||||||
|
|
||||||
|
return await GetPostAsync(post.Id); // ✅ DTO dönülüyor
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeletePostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
|
||||||
|
// Check if user is author or has permission
|
||||||
|
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Delete"))
|
||||||
|
{
|
||||||
|
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update category post count
|
||||||
|
var category = await _categoryRepository.GetAsync(post.CategoryId);
|
||||||
|
category.DecrementPostCount();
|
||||||
|
await _categoryRepository.UpdateAsync(category);
|
||||||
|
|
||||||
|
await _postRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPostDto> PublishPostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
|
||||||
|
// Check if user is author or has permission
|
||||||
|
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Publish"))
|
||||||
|
{
|
||||||
|
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Publish();
|
||||||
|
await _postRepository.UpdateAsync(post);
|
||||||
|
|
||||||
|
return await GetPostAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPostDto> UnpublishPostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
|
||||||
|
// Check if user is author or has permission
|
||||||
|
if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.BlogManagement.Publish"))
|
||||||
|
{
|
||||||
|
throw new Volo.Abp.Authorization.AbpAuthorizationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Unpublish();
|
||||||
|
await _postRepository.UpdateAsync(post);
|
||||||
|
|
||||||
|
return await GetPostAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Blog Category methods
|
||||||
|
// [AllowAnonymous]
|
||||||
|
// public async Task<List<BlogCategoryDto>> GetCategoriesAsync()
|
||||||
|
// {
|
||||||
|
// var categories = await _categoryRepository.GetListAsync();
|
||||||
|
|
||||||
|
// var postQuery = await _postRepository.GetQueryableAsync();
|
||||||
|
|
||||||
|
// var groupedCounts = postQuery
|
||||||
|
// .Where(p => p.IsPublished) // sadece yayınlanmış yazılar
|
||||||
|
// .GroupBy(p => p.CategoryId)
|
||||||
|
// .Select(g => new { CategoryId = g.Key, Count = g.Count() })
|
||||||
|
// .ToList();
|
||||||
|
|
||||||
|
// var dtoList = ObjectMapper.Map<List<BlogCategory>, List<BlogCategoryDto>>(categories);
|
||||||
|
|
||||||
|
// foreach (var dto in dtoList)
|
||||||
|
// {
|
||||||
|
// dto.PostCount = groupedCounts
|
||||||
|
// .FirstOrDefault(x => x.CategoryId == dto.Id)?.Count ?? 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return dtoList;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async Task<BlogCategoryDto> GetCategoryAsync(Guid id)
|
||||||
|
{
|
||||||
|
var category = await _categoryRepository.GetAsync(id);
|
||||||
|
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize("App.BlogManagement.Create")]
|
||||||
|
public async Task<BlogCategoryDto> CreateCategoryAsync(CreateUpdateBlogCategoryDto input)
|
||||||
|
{
|
||||||
|
var category = new BlogCategory(
|
||||||
|
GuidGenerator.Create(),
|
||||||
|
input.Name,
|
||||||
|
input.Slug,
|
||||||
|
input.Description,
|
||||||
|
CurrentTenant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
category.Icon = input.Icon;
|
||||||
|
category.DisplayOrder = input.DisplayOrder;
|
||||||
|
category.IsActive = input.IsActive;
|
||||||
|
|
||||||
|
await _categoryRepository.InsertAsync(category);
|
||||||
|
|
||||||
|
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize("App.BlogManagement.Update")]
|
||||||
|
public async Task<BlogCategoryDto> UpdateCategoryAsync(Guid id, CreateUpdateBlogCategoryDto 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;
|
||||||
|
|
||||||
|
await _categoryRepository.UpdateAsync(category);
|
||||||
|
|
||||||
|
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize("App.BlogManagement.Delete")]
|
||||||
|
public async Task DeleteCategoryAsync(Guid id)
|
||||||
|
{
|
||||||
|
// Check if category has posts
|
||||||
|
var hasPost = await _postRepository.AnyAsync(x => x.CategoryId == id);
|
||||||
|
if (hasPost)
|
||||||
|
{
|
||||||
|
throw new Volo.Abp.BusinessException("Cannot delete category with posts");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _categoryRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class Program
|
||||||
case DatabaseProvider.PostgreSql:
|
case DatabaseProvider.PostgreSql:
|
||||||
loggerConfig = loggerConfig.WriteTo.PostgreSQL(
|
loggerConfig = loggerConfig.WriteTo.PostgreSQL(
|
||||||
connectionString: configuration.GetConnectionString(DefaultDatabaseProvider),
|
connectionString: configuration.GetConnectionString(DefaultDatabaseProvider),
|
||||||
tableName: PlatformConsts.DbTablePrefix + "LogEntry",
|
tableName: PlatformConsts.SelectCommandByTableName("LogEntry"),
|
||||||
columnOptions: columnWriters,
|
columnOptions: columnWriters,
|
||||||
needAutoCreateTable: true,
|
needAutoCreateTable: true,
|
||||||
respectCase: true
|
respectCase: true
|
||||||
|
|
@ -52,7 +52,7 @@ public class Program
|
||||||
case DatabaseProvider.SqlServer:
|
case DatabaseProvider.SqlServer:
|
||||||
loggerConfig = loggerConfig.WriteTo.MSSqlServer(
|
loggerConfig = loggerConfig.WriteTo.MSSqlServer(
|
||||||
connectionString: configuration.GetConnectionString(DefaultDatabaseProvider),
|
connectionString: configuration.GetConnectionString(DefaultDatabaseProvider),
|
||||||
tableName: PlatformConsts.DbTablePrefix + "LogEntry",
|
tableName: PlatformConsts.SelectCommandByTableName("LogEntry"),
|
||||||
autoCreateSqlTable: true,
|
autoCreateSqlTable: true,
|
||||||
columnOptions: new Serilog.Sinks.MSSqlServer.ColumnOptions()
|
columnOptions: new Serilog.Sinks.MSSqlServer.ColumnOptions()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.vah6gtb83uo"
|
"revision": "0.3u9qv452np"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { PagedResultDto } from '../abp'
|
||||||
|
|
||||||
export interface BlogPost {
|
export interface BlogPost {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
|
@ -39,6 +41,11 @@ export interface BlogCategory {
|
||||||
tenantId?: string
|
tenantId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlogPostAndCategoriesDto {
|
||||||
|
posts: PagedResultDto<BlogPost>
|
||||||
|
categories: BlogCategory[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateUpdateBlogPostDto {
|
export interface CreateUpdateBlogPostDto {
|
||||||
title: string
|
title: string
|
||||||
slug: string
|
slug: string
|
||||||
|
|
@ -71,11 +78,3 @@ export interface BlogListParams {
|
||||||
authorId?: string
|
authorId?: string
|
||||||
sortBy?: 'latest' | 'popular' | 'trending'
|
sortBy?: 'latest' | 'popular' | 'trending'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
|
||||||
items: T[]
|
|
||||||
totalCount: number
|
|
||||||
pageNumber: number
|
|
||||||
pageSize: number
|
|
||||||
totalPages: number
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
import { BlogCategory, BlogListParams, BlogPost, CreateUpdateBlogCategoryDto, CreateUpdateBlogPostDto, PaginatedResponse } from '@/proxy/blog/blog';
|
import {
|
||||||
|
BlogCategory,
|
||||||
|
BlogListParams,
|
||||||
|
BlogPost,
|
||||||
|
BlogPostAndCategoriesDto,
|
||||||
|
CreateUpdateBlogCategoryDto,
|
||||||
|
CreateUpdateBlogPostDto,
|
||||||
|
} from '@/proxy/blog/blog'
|
||||||
import apiService from '@/services/api.service'
|
import apiService from '@/services/api.service'
|
||||||
|
|
||||||
class BlogService {
|
class BlogService {
|
||||||
async getPosts(params: BlogListParams = {}): Promise<PaginatedResponse<BlogPost>> {
|
async getPosts(params: BlogListParams = {}): Promise<BlogPostAndCategoriesDto> {
|
||||||
const response = await apiService.fetchData<PaginatedResponse<BlogPost>>({
|
const response = await apiService.fetchData<BlogPostAndCategoriesDto>({
|
||||||
url: '/api/app/blog/posts',
|
url: '/api/app/blog/post-list',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
|
|
@ -13,10 +20,10 @@ class BlogService {
|
||||||
|
|
||||||
async getPostBySlug(slug: string): Promise<BlogPost> {
|
async getPostBySlug(slug: string): Promise<BlogPost> {
|
||||||
const response = await apiService.fetchData<BlogPost>({
|
const response = await apiService.fetchData<BlogPost>({
|
||||||
url : `/api/app/blog/post-by-slug?slug=${slug}`,
|
url: `/api/app/blog/post-by-slug?slug=${slug}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
return response.data;
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPost(idOrSlug: string): Promise<BlogPost> {
|
async getPost(idOrSlug: string): Promise<BlogPost> {
|
||||||
|
|
@ -68,13 +75,13 @@ class BlogService {
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCategories(): Promise<BlogCategory[]> {
|
// async getCategories(): Promise<BlogCategory[]> {
|
||||||
const response = await apiService.fetchData<BlogCategory[]>({
|
// const response = await apiService.fetchData<BlogCategory[]>({
|
||||||
url: '/api/app/blog/categories',
|
// url: '/api/app/blog/categories',
|
||||||
method: 'GET',
|
// method: 'GET',
|
||||||
})
|
// })
|
||||||
return response.data
|
// return response.data
|
||||||
}
|
// }
|
||||||
|
|
||||||
async deleteComment(id: string): Promise<void> {
|
async deleteComment(id: string): Promise<void> {
|
||||||
await apiService.fetchData({
|
await apiService.fetchData({
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import {
|
import { FaCalendarAlt, FaUser, FaTag, FaSearch } from 'react-icons/fa'
|
||||||
FaCalendarAlt,
|
|
||||||
FaUser,
|
|
||||||
FaTag,
|
|
||||||
FaSearch
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import 'dayjs/locale/tr'
|
import 'dayjs/locale/tr'
|
||||||
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
|
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
|
||||||
|
|
@ -33,19 +28,24 @@ const Blog = () => {
|
||||||
const loadBlogData = async () => {
|
const loadBlogData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const [postsData, categoriesData] = await Promise.all([
|
|
||||||
blogService.getPosts({
|
|
||||||
page: currentPage,
|
|
||||||
pageSize: 10,
|
|
||||||
categoryId: selectedCategory,
|
|
||||||
search: searchQuery,
|
|
||||||
}),
|
|
||||||
blogService.getCategories(),
|
|
||||||
])
|
|
||||||
|
|
||||||
setPosts(postsData.items.filter((a) => a.isPublished))
|
const postsData = await blogService.getPosts({
|
||||||
setTotalPages(postsData.totalPages)
|
page: currentPage,
|
||||||
setCategories(categoriesData.filter((a) => a.isActive))
|
pageSize: 10,
|
||||||
|
categoryId: selectedCategory,
|
||||||
|
search: searchQuery,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
postsData.posts &&
|
||||||
|
postsData.posts.items &&
|
||||||
|
postsData.posts.totalCount &&
|
||||||
|
postsData.categories
|
||||||
|
) {
|
||||||
|
setPosts(postsData.posts.items.filter((a) => a.isPublished))
|
||||||
|
setTotalPages(postsData.posts.totalCount / 10)
|
||||||
|
setCategories(postsData.categories.filter((a) => a.isActive))
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Blog verileri yüklenemedi:', error)
|
console.error('Blog verileri yüklenemedi:', error)
|
||||||
setPosts([])
|
setPosts([])
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,9 @@ const BlogDetail: React.FC = () => {
|
||||||
className="prose max-w-none text-gray-800"
|
className="prose max-w-none text-gray-800"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html:
|
__html:
|
||||||
currentLang == 'tr' ? translate(blogPost.contentTr!) : translate(blogPost.contentEn!),
|
currentLang == 'tr'
|
||||||
|
? translate('::' + blogPost.contentTr!)
|
||||||
|
: translate('::' + blogPost.contentEn!),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue