Blog Application Service güncellemeleri

This commit is contained in:
Sedat ÖZTÜRK 2025-08-20 09:22:04 +03:00
parent 7386ff91fa
commit d891ae3f85
9 changed files with 426 additions and 340 deletions

View file

@ -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; }
}

View file

@ -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);

View file

@ -11,8 +11,8 @@ 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] [Authorize]
public class BlogAppService : PlatformAppService, IBlogAppService public class BlogAppService : PlatformAppService, IBlogAppService
{ {
@ -35,56 +35,125 @@ namespace Kurs.Platform.Blog
// Blog Post methods // Blog Post methods
[AllowAnonymous] [AllowAnonymous]
public async Task<PagedResultDto<BlogPostListDto>> GetPostsAsync(GetBlogPostsInput input) public async Task<BlogPostAndCategoriesDto> GetPostListAsync(GetBlogPostsInput input)
{ {
var allPosts = await _postRepository.GetListAsync(); // Tüm kayıtlar memory'ye alınır // IQueryable
var postQuery = await _postRepository.GetQueryableAsync();
var filtered = allPosts.Where(post => // 🔎 Arama
if (!input.Search.IsNullOrWhiteSpace())
{ {
var searchMatch = string.IsNullOrWhiteSpace(input.Search) || postQuery = postQuery.Where(p =>
post.ContentTr.Contains(input.Search, StringComparison.OrdinalIgnoreCase) || p.ContentTr.Contains(input.Search) ||
post.ContentEn.Contains(input.Search, StringComparison.OrdinalIgnoreCase); p.ContentEn.Contains(input.Search));
}
var categoryMatch = !input.CategoryId.HasValue || post.CategoryId == input.CategoryId.Value; // 📁 Kategori filtresi
if (input.CategoryId.HasValue)
{
postQuery = postQuery.Where(p => p.CategoryId == input.CategoryId.Value);
}
return searchMatch && categoryMatch; // Toplam adet (sayfalama öncesi)
}).ToList(); var totalCount = await AsyncExecuter.CountAsync(postQuery);
var totalCount = filtered.Count; // Sayfalama + sıralama
var pagedPosts = await AsyncExecuter.ToListAsync(
var pagedPosts = filtered postQuery
.OrderByDescending(x => x.CreationTime) .OrderByDescending(p => p.CreationTime)
.Skip(input.SkipCount) .PageBy(input)
.Take(input.MaxResultCount) );
.ToList();
// Sayfadaki kategori kayıtları
var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList(); var categoryIds = pagedPosts.Select(x => x.CategoryId).Distinct().ToList();
var categories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id)); var pageCategories = await _categoryRepository.GetListAsync(x => categoryIds.Contains(x.Id));
var categoryDict = categories.ToDictionary(x => x.Id, x => x); var categoryDict = pageCategories.ToDictionary(x => x.Id, x => x);
var postIds = pagedPosts.Select(x => x.Id).ToList();
// Post DTO mapping
var postDtos = pagedPosts.Select(post => var postDtos = pagedPosts.Select(post =>
{ {
var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post); var dto = ObjectMapper.Map<BlogPost, BlogPostListDto>(post);
if (categoryDict.TryGetValue(post.CategoryId, out var c))
if (categoryDict.TryGetValue(post.CategoryId, out var category))
{ {
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category); dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(c);
} }
dto.Author = new AuthorDto dto.Author = new AuthorDto { Id = post.AuthorId, Name = "User" };
{
Id = post.AuthorId,
Name = "User"
};
return dto; return dto;
}).ToList(); }).ToList();
return new PagedResultDto<BlogPostListDto>(totalCount, postDtos); // ----------- 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] [AllowAnonymous]
public async Task<BlogPostDto> GetPostAsync(Guid id) public async Task<BlogPostDto> GetPostAsync(Guid id)
{ {
@ -227,30 +296,30 @@ namespace Kurs.Platform.Blog
return await GetPostAsync(id); return await GetPostAsync(id);
} }
// Blog Category methods // // Blog Category methods
[AllowAnonymous] // [AllowAnonymous]
public async Task<List<BlogCategoryDto>> GetCategoriesAsync() // public async Task<List<BlogCategoryDto>> GetCategoriesAsync()
{ // {
var categories = await _categoryRepository.GetListAsync(); // var categories = await _categoryRepository.GetListAsync();
var postQuery = await _postRepository.GetQueryableAsync(); // var postQuery = await _postRepository.GetQueryableAsync();
var groupedCounts = postQuery // var groupedCounts = postQuery
.Where(p => p.IsPublished) // sadece yayınlanmış yazılar // .Where(p => p.IsPublished) // sadece yayınlanmış yazılar
.GroupBy(p => p.CategoryId) // .GroupBy(p => p.CategoryId)
.Select(g => new { CategoryId = g.Key, Count = g.Count() }) // .Select(g => new { CategoryId = g.Key, Count = g.Count() })
.ToList(); // .ToList();
var dtoList = ObjectMapper.Map<List<BlogCategory>, List<BlogCategoryDto>>(categories); // var dtoList = ObjectMapper.Map<List<BlogCategory>, List<BlogCategoryDto>>(categories);
foreach (var dto in dtoList) // foreach (var dto in dtoList)
{ // {
dto.PostCount = groupedCounts // dto.PostCount = groupedCounts
.FirstOrDefault(x => x.CategoryId == dto.Id)?.Count ?? 0; // .FirstOrDefault(x => x.CategoryId == dto.Id)?.Count ?? 0;
} // }
return dtoList; // return dtoList;
} // }
public async Task<BlogCategoryDto> GetCategoryAsync(Guid id) public async Task<BlogCategoryDto> GetCategoryAsync(Guid id)
{ {
@ -309,4 +378,3 @@ namespace Kurs.Platform.Blog
} }
} }
}

View file

@ -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()
); );

View file

@ -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"), {

View file

@ -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
}

View file

@ -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,
}) })
@ -16,7 +23,7 @@ class BlogService {
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({

View file

@ -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({ const postsData = await blogService.getPosts({
page: currentPage, page: currentPage,
pageSize: 10, pageSize: 10,
categoryId: selectedCategory, categoryId: selectedCategory,
search: searchQuery, search: searchQuery,
}), })
blogService.getCategories(),
])
setPosts(postsData.items.filter((a) => a.isPublished)) if (
setTotalPages(postsData.totalPages) postsData.posts &&
setCategories(categoriesData.filter((a) => a.isActive)) 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([])

View file

@ -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>