From 2a1f06b2f4e87c447002bf0761673664207ce1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Fri, 20 Jun 2025 00:42:16 +0300 Subject: [PATCH] =?UTF-8?q?Blog=20sistemindeki=20g=C3=BCncellemeler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Blog/BlogPostDto.cs | 2 + .../Blog/BlogAppService.cs | 18 +- .../Seeds/PlatformDataSeeder.cs | 50 +- .../Seeds/SeederData.json | 754 +++++++++++++++--- .../Seeds/SeederDto.cs | 29 +- api/src/Kurs.Platform.Domain/Blog/BlogPost.cs | 5 + ...19205823_AddBlogForumEntities.Designer.cs} | 5 +- ...=> 20250619205823_AddBlogForumEntities.cs} | 1 + .../PlatformDbContextModelSnapshot.cs | 3 + company/src/pages/Blog.tsx | 16 +- company/src/pages/BlogDetail.tsx | 118 ++- company/src/services/api/blog.service.ts | 113 ++- ui/dev-dist/sw.js | 2 +- ui/src/services/blog.service.ts | 248 +++--- ui/src/views/blog/BlogManagement.tsx | 90 +-- 15 files changed, 1072 insertions(+), 382 deletions(-) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250619131606_AddBlogForumEntities.Designer.cs => 20250619205823_AddBlogForumEntities.Designer.cs} (99%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250619131606_AddBlogForumEntities.cs => 20250619205823_AddBlogForumEntities.cs} (99%) diff --git a/api/src/Kurs.Platform.Application.Contracts/Blog/BlogPostDto.cs b/api/src/Kurs.Platform.Application.Contracts/Blog/BlogPostDto.cs index 362ed010..daedbf77 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Blog/BlogPostDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Blog/BlogPostDto.cs @@ -46,6 +46,7 @@ namespace Kurs.Platform.Blog public string Slug { get; set; } public string Content { get; set; } public string Summary { get; set; } + public string ReadTime { get; set; } public string CoverImage { get; set; } public Guid CategoryId { get; set; } public List Tags { get; set; } @@ -62,6 +63,7 @@ namespace Kurs.Platform.Blog public string Title { get; set; } public string Slug { get; set; } public string Summary { get; set; } + public string ReadTime { get; set; } public string CoverImage { get; set; } public BlogCategoryDto Category { get; set; } diff --git a/api/src/Kurs.Platform.Application/Blog/BlogAppService.cs b/api/src/Kurs.Platform.Application/Blog/BlogAppService.cs index 517b0cb0..e172dc8a 100644 --- a/api/src/Kurs.Platform.Application/Blog/BlogAppService.cs +++ b/api/src/Kurs.Platform.Application/Blog/BlogAppService.cs @@ -104,7 +104,7 @@ namespace Kurs.Platform.Blog dto.Author = new AuthorDto { Id = post.AuthorId, - Name = post.CreatorId.HasValue ? "User" : "Unknown" // You should get actual user name + Name = post.CreatorId.HasValue ? "User" : "Unknown" }; postDtos.Add(dto); @@ -164,6 +164,8 @@ namespace Kurs.Platform.Blog input.Slug, input.Content, input.Summary, + input.ReadTime, + input.CoverImage, input.CategoryId, _currentUser.Id.Value, CurrentTenant.Id @@ -202,7 +204,7 @@ namespace Kurs.Platform.Blog var post = await _postRepository.GetAsync(id); // Check if user is author or has permission - if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("Blog.Posts.Update")) + if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.Blog.Update")) { throw new Volo.Abp.Authorization.AbpAuthorizationException(); } @@ -249,7 +251,7 @@ namespace Kurs.Platform.Blog var post = await _postRepository.GetAsync(id); // Check if user is author or has permission - if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("Blog.Posts.Delete")) + if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.Blog.Delete")) { throw new Volo.Abp.Authorization.AbpAuthorizationException(); } @@ -267,7 +269,7 @@ namespace Kurs.Platform.Blog var post = await _postRepository.GetAsync(id); // Check if user is author or has permission - if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("Blog.Posts.Publish")) + if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.Blog.Publish")) { throw new Volo.Abp.Authorization.AbpAuthorizationException(); } @@ -283,7 +285,7 @@ namespace Kurs.Platform.Blog var post = await _postRepository.GetAsync(id); // Check if user is author or has permission - if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("Blog.Posts.Publish")) + if (post.AuthorId != _currentUser.Id && !await AuthorizationService.IsGrantedAsync("App.Blog.Publish")) { throw new Volo.Abp.Authorization.AbpAuthorizationException(); } @@ -357,7 +359,7 @@ namespace Kurs.Platform.Blog return ObjectMapper.Map(category); } - [Authorize("Blog.Categories.Create")] + [Authorize("App.Blog.Create")] public async Task CreateCategoryAsync(CreateUpdateBlogCategoryDto input) { var category = new BlogCategory( @@ -377,7 +379,7 @@ namespace Kurs.Platform.Blog return ObjectMapper.Map(category); } - [Authorize("Blog.Categories.Update")] + [Authorize("App.Blog.Update")] public async Task UpdateCategoryAsync(Guid id, CreateUpdateBlogCategoryDto input) { var category = await _categoryRepository.GetAsync(id); @@ -394,7 +396,7 @@ namespace Kurs.Platform.Blog return ObjectMapper.Map(category); } - [Authorize("Blog.Categories.Delete")] + [Authorize("App.Blog.Delete")] public async Task DeleteCategoryAsync(Guid id) { // Check if category has posts diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs index a71e3959..0c251843 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/PlatformDataSeeder.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading.Tasks; using Kurs.Languages.Entities; using Kurs.Notifications.Entities; +using Kurs.Platform.Blog; using Kurs.Platform.Charts.Dto; using Kurs.Platform.Entities; using Kurs.Platform.Enums; @@ -48,6 +49,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency private readonly IRepository _skillLevelRepository; private readonly IRepository _contactTagRepository; private readonly IRepository _contactTitleRepository; + private readonly IRepository _blogCategoryRepository; + private readonly IRepository _blogPostsRepository; public PlatformDataSeeder( IRepository languages, @@ -73,7 +76,9 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency IRepository skillRepository, IRepository skillLevelRepository, IRepository contactTagRepository, - IRepository contactTitleRepository + IRepository contactTitleRepository, + IRepository blogCategoryRepository, + IRepository blogPostsRepository ) { _languages = languages; @@ -100,6 +105,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency _skillLevelRepository = skillLevelRepository; _contactTagRepository = contactTagRepository; _contactTitleRepository = contactTitleRepository; + _blogCategoryRepository = blogCategoryRepository; + _blogPostsRepository = blogPostsRepository; } private static IConfigurationRoot BuildConfiguration() @@ -543,5 +550,46 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency }); } } + + foreach (var item in items.BlogCategories) + { + var exists = await _blogCategoryRepository.AnyAsync(x => x.Name == item.Name); + + if (!exists) + { + var newCategory = new BlogCategory( + item.Id, + item.Name, + item.Slug, + item.Description + ) + { + DisplayOrder = item.DisplayOrder, + PostCount = 1 + }; + + await _blogCategoryRepository.InsertAsync(newCategory); + } + } + + foreach (var item in items.BlogPosts) + { + var exists = await _blogPostsRepository.AnyAsync(x => x.Title == item.Title); + + if (!exists) + { + await _blogPostsRepository.InsertAsync(new BlogPost( + item.Id, + item.Title, + item.Slug, + item.Content, + item.Summary, + item.ReadTime, + item.CoverImage, + item.CategoryId, + item.AuthorId + )); + } + } } } diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json index b3262ae1..21e5b26b 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederData.json @@ -533,7 +533,7 @@ "key": "App.Forum", "en": "Forum", "tr": "Forum" - }, + }, { "resourceName": "Platform", "key": "App.Home", @@ -4640,7 +4640,10 @@ "descriptionKey": "Abp.Localization.DefaultLanguage.Description", "defaultValue": "en", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -4674,7 +4677,10 @@ "descriptionKey": "Abp.Localization.Timezone.Description", "defaultValue": "UTC", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -4830,7 +4836,11 @@ "descriptionKey": "App.SiteManagement.Theme.Style.Description", "defaultValue": "dx.light.compact", "isVisibleToClients": true, - "providers": ["U", "G", "D"], + "providers": [ + "U", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -4878,7 +4888,10 @@ "descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description", "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -4894,7 +4907,10 @@ "descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description", "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.SiteManagement", @@ -4910,7 +4926,11 @@ "descriptionKey": "App.Sender.Sms.PostaGuvercini.Url.Description", "defaultValue": "https://www.postaguvercini.com/api_http", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -4926,7 +4946,11 @@ "descriptionKey": "App.Sender.Sms.PostaGuvercini.Username.Description", "defaultValue": "2AIlj4QlCrvlbDDBS/712A==", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": true, "mainGroupKey": "App.Sender", @@ -4942,7 +4966,11 @@ "descriptionKey": "App.Sender.Sms.PostaGuvercini.Password.Description", "defaultValue": "oTuwyZM9sxfJI+jDH5wJAw==", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": true, "mainGroupKey": "App.Sender", @@ -4958,7 +4986,11 @@ "descriptionKey": "App.Sender.WhatsApp.Url.Description", "defaultValue": "https://graph.facebook.com/v21.0", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -4974,7 +5006,11 @@ "descriptionKey": "App.Sender.WhatsApp.PhoneNumberId.Description", "defaultValue": "442035112335974", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -4990,7 +5026,11 @@ "descriptionKey": "App.Sender.WhatsApp.Token.Description", "defaultValue": "EAANoftqZAJ64BO5oPwXPqniUtNGF70u8TKvQVzGZBaYQh5UY8fYrgQkcXP9UbQUqT9PWRah1L7TzcBIiWQMacT8AkmZB33AP1begLoywIZCsQSdBSUz21GQaCowfVosYgBoXSyqH8irSBPQDLIjxxVxrC2n76SD9X6zPXeHgOqIPY92DqJXplstWrlhtZCAZDZD", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -5006,7 +5046,11 @@ "descriptionKey": "App.Sender.WhatsApp.TemplateName.Description", "defaultValue": "kurs_platform_notification", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -5022,7 +5066,10 @@ "descriptionKey": "App.Sender.Rocket.Url.Description", "defaultValue": "https://chat.sozsoft.com/api/v1", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -5038,7 +5085,10 @@ "descriptionKey": "App.Sender.Rocket.UserId.Description", "defaultValue": "LfpzPjzag4QJXm84N", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -5054,7 +5104,10 @@ "descriptionKey": "App.Sender.Rocket.Token.Description", "defaultValue": "jvqALawvXn0Q7c6FfHJV3h58DCHDfQLgFF5y7oIc7oc", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "App.Sender", @@ -5070,7 +5123,11 @@ "descriptionKey": "Abp.Mailing.DefaultFromDisplayName.Description", "defaultValue": "Kurs", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5086,7 +5143,11 @@ "descriptionKey": "Abp.Mailing.DefaultFromAddress.Description", "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5102,7 +5163,11 @@ "descriptionKey": "Abp.Mailing.Smtp.UserName.Description", "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5118,7 +5183,11 @@ "descriptionKey": "Abp.Mailing.Smtp.Password.Description", "defaultValue": "QT9L7BCl1CT/1Hq19HoSlQ==", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": true, "mainGroupKey": "Abp.Mailing", @@ -5134,7 +5203,11 @@ "descriptionKey": "Abp.Mailing.Smtp.Host.Description", "defaultValue": "127.0.0.1", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5150,7 +5223,11 @@ "descriptionKey": "Abp.Mailing.Smtp.Port.Description", "defaultValue": "25", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5166,7 +5243,11 @@ "descriptionKey": "Abp.Mailing.Smtp.Domain.Description", "defaultValue": "sozsoft.com", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5182,7 +5263,11 @@ "descriptionKey": "Abp.Mailing.Smtp.EnableSsl.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5198,7 +5283,11 @@ "descriptionKey": "Abp.Mailing.AWS.Profile.Description", "defaultValue": "mail-sdk-user", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5214,7 +5303,11 @@ "descriptionKey": "Abp.Mailing.AWS.Region.Description", "defaultValue": "eu-central-1", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5230,7 +5323,11 @@ "descriptionKey": "Abp.Mailing.AWS.AccessKey.Description", "defaultValue": "aXW8L21rP6dPO6Txj76Be2FCpWRBa25EMrSAVL76", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5246,7 +5343,11 @@ "descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description", "defaultValue": "AKIATULUYBLX4IY3S2P1", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Mailing", @@ -5262,7 +5363,10 @@ "descriptionKey": "Abp.Account.IsSelfRegistrationEnabled.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5278,7 +5382,10 @@ "descriptionKey": "Abp.Account.EnableLocalLogin.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5294,7 +5401,11 @@ "descriptionKey": "Abp.Account.TwoFactor.Enabled.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5310,7 +5421,10 @@ "descriptionKey": "Abp.Account.Captcha.MaxFailedAccessAttempts.Description", "defaultValue": "3", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5326,7 +5440,10 @@ "descriptionKey": "Abp.Account.Captcha.EndPoint.Description", "defaultValue": "https://challenges.cloudflare.com/turnstile/v0/siteverify", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5342,7 +5459,10 @@ "descriptionKey": "Abp.Account.Captcha.SiteKey.Description", "defaultValue": "0x4AAAAAAAGadwQME-GSYuJU", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5358,7 +5478,10 @@ "descriptionKey": "Abp.Account.Captcha.SecretKey.Description", "defaultValue": "0x4AAAAAAAGad_f_WP47IcNBs9FTu5DhNX8", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Account", @@ -5374,7 +5497,11 @@ "descriptionKey": "Abp.Identity.Profile.General.RequireVerifiedAccount.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5390,7 +5517,11 @@ "descriptionKey": "Abp.Identity.Profile.General.BlacklistedEmailProviders.Description", "defaultValue": "gmail.com\r\nyahoo.com\r\nhotmail.com", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5406,7 +5537,11 @@ "descriptionKey": "Abp.Identity.Password.ForceUsersToPeriodicallyChangePassword.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5422,7 +5557,11 @@ "descriptionKey": "Abp.Identity.Password.PasswordChangePeriodDays.Description", "defaultValue": "0", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5438,7 +5577,11 @@ "descriptionKey": "Abp.Identity.Password.RequiredLength.Description", "defaultValue": "6", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5454,7 +5597,11 @@ "descriptionKey": "Abp.Identity.Password.RequiredUniqueChars.Description", "defaultValue": "1", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5470,7 +5617,11 @@ "descriptionKey": "Abp.Identity.Password.RequireNonAlphanumeric.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5486,7 +5637,11 @@ "descriptionKey": "Abp.Identity.Password.RequireLowercase.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5502,7 +5657,11 @@ "descriptionKey": "Abp.Identity.Password.RequireUppercase.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5518,7 +5677,11 @@ "descriptionKey": "Abp.Identity.Password.RequireDigit.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5534,7 +5697,11 @@ "descriptionKey": "Abp.Identity.Lockout.AllowedForNewUsers.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5550,7 +5717,11 @@ "descriptionKey": "Abp.Identity.Lockout.LockoutDuration.Description", "defaultValue": "300", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5566,7 +5737,11 @@ "descriptionKey": "Abp.Identity.Lockout.MaxFailedAccessAttempts.Description", "defaultValue": "5", "isVisibleToClients": false, - "providers": ["T", "G", "D"], + "providers": [ + "T", + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5582,7 +5757,10 @@ "descriptionKey": "Abp.Identity.SignIn.RequireConfirmedEmail.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5598,7 +5776,10 @@ "descriptionKey": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber.Description", "defaultValue": "False", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5614,7 +5795,10 @@ "descriptionKey": "Abp.Identity.User.IsUserNameUpdateEnabled.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5630,7 +5814,10 @@ "descriptionKey": "Abp.Identity.User.IsEmailUpdateEnabled.Description", "defaultValue": "True", "isVisibleToClients": false, - "providers": ["G", "D"], + "providers": [ + "G", + "D" + ], "isInherited": false, "isEncrypted": false, "mainGroupKey": "Abp.Identity", @@ -5999,7 +6186,7 @@ "Icon": "FcTemplate", "RequiredPermissionName": "App.Blog", "IsDisabled": false - }, + }, { "ParentCode": "App.Saas", "Code": "App.Forum", @@ -6175,7 +6362,7 @@ { "Name": "App.Forum", "DisplayName": "App.Forum" - } + } ], "PermissionDefinitionRecords": [ { @@ -7410,6 +7597,14 @@ "IsEnabled": true, "MultiTenancySide": 2 }, + { + "GroupName": "App.Blog", + "Name": "App.Blog.Publish", + "ParentName": "App.Blog", + "DisplayName": "Export", + "IsEnabled": true, + "MultiTenancySide": 2 + }, { "GroupName": "App.Blog", "Name": "App.Blog.Update", @@ -7452,39 +7647,126 @@ } ], "Sectors": [ - { "Name": "Ambalaj", "FullName": "" }, - { "Name": "Demir Çelik", "FullName": "" }, - { "Name": "Diğer", "FullName": "" }, - { "Name": "Elektrik ve Elektronik", "FullName": "" }, - { "Name": "Giyim", "FullName": "" }, - { "Name": "Güvenlik", "FullName": "" }, - { "Name": "Gıda", "FullName": "" }, - { "Name": "Hizmet-servis", "FullName": "" }, - { "Name": "Hırdavat ve Nalburiye", "FullName": "" }, - { "Name": "Isıtma, Soğutma ve Havalandırma", "FullName": "" }, - { "Name": "İnşaat", "FullName": "" }, - { "Name": "Kantar", "FullName": "" }, - { "Name": "Kimyasal", "FullName": "" }, - { "Name": "Kırtasiye", "FullName": "" }, - { "Name": "Laboratuar ve Test Ürünleri", "FullName": "" }, - { "Name": "Makina", "FullName": "" }, - { "Name": "Matbaa", "FullName": "" }, - { "Name": "Ofis", "FullName": "" }, - { "Name": "Oto Tamir-Servis", "FullName": "" }, - { "Name": "Pnomatik", "FullName": "" }, - { "Name": "Sarf", "FullName": "" }, - { "Name": "Sağlık", "FullName": "" }, - { "Name": "Tartı", "FullName": "" }, - { "Name": "Transpalet", "FullName": "" }, - { "Name": "Yedek Parça", "FullName": "" } + { + "Name": "Ambalaj", + "FullName": "" + }, + { + "Name": "Demir Çelik", + "FullName": "" + }, + { + "Name": "Diğer", + "FullName": "" + }, + { + "Name": "Elektrik ve Elektronik", + "FullName": "" + }, + { + "Name": "Giyim", + "FullName": "" + }, + { + "Name": "Güvenlik", + "FullName": "" + }, + { + "Name": "Gıda", + "FullName": "" + }, + { + "Name": "Hizmet-servis", + "FullName": "" + }, + { + "Name": "Hırdavat ve Nalburiye", + "FullName": "" + }, + { + "Name": "Isıtma, Soğutma ve Havalandırma", + "FullName": "" + }, + { + "Name": "İnşaat", + "FullName": "" + }, + { + "Name": "Kantar", + "FullName": "" + }, + { + "Name": "Kimyasal", + "FullName": "" + }, + { + "Name": "Kırtasiye", + "FullName": "" + }, + { + "Name": "Laboratuar ve Test Ürünleri", + "FullName": "" + }, + { + "Name": "Makina", + "FullName": "" + }, + { + "Name": "Matbaa", + "FullName": "" + }, + { + "Name": "Ofis", + "FullName": "" + }, + { + "Name": "Oto Tamir-Servis", + "FullName": "" + }, + { + "Name": "Pnomatik", + "FullName": "" + }, + { + "Name": "Sarf", + "FullName": "" + }, + { + "Name": "Sağlık", + "FullName": "" + }, + { + "Name": "Tartı", + "FullName": "" + }, + { + "Name": "Transpalet", + "FullName": "" + }, + { + "Name": "Yedek Parça", + "FullName": "" + } ], "UomCategories": [ - { "Name": "Adet" }, - { "Name": "Ağırlık" }, - { "Name": "Çalışma Süresi" }, - { "Name": "Uzunluk / Mesafe" }, - { "Name": "Yüzey" }, - { "Name": "Hacim" } + { + "Name": "Adet" + }, + { + "Name": "Ağırlık" + }, + { + "Name": "Çalışma Süresi" + }, + { + "Name": "Uzunluk / Mesafe" + }, + { + "Name": "Yüzey" + }, + { + "Name": "Hacim" + } ], "Uoms": [ { @@ -8906,12 +9188,24 @@ } ], "CountryGroups": [ - { "Name": "European Union" }, - { "Name": "Güney Amerika" }, - { "Name": "SEPA Ülkeleri" }, - { "Name": "Körfez İşbirliği Konseyi (KİK)" }, - { "Name": "Eurasian Economic Union" }, - { "Name": "İsviçre ve Lihtenştayn" } + { + "Name": "European Union" + }, + { + "Name": "Güney Amerika" + }, + { + "Name": "SEPA Ülkeleri" + }, + { + "Name": "Körfez İşbirliği Konseyi (KİK)" + }, + { + "Name": "Eurasian Economic Union" + }, + { + "Name": "İsviçre ve Lihtenştayn" + } ], "Countries": [ { @@ -19547,32 +19841,111 @@ "CountryCode": "BE" } ], - "SkillTypes": [{ "Name": "Diller" }, { "Name": "Soft Skills" }], + "SkillTypes": [ + { + "Name": "Diller" + }, + { + "Name": "Soft Skills" + } + ], "Skills": [ - { "TypeName": "Diller", "Name": "Spanish" }, - { "TypeName": "Diller", "Name": "Fransızca" }, - { "TypeName": "Diller", "Name": "English" }, - { "TypeName": "Diller", "Name": "German" }, - { "TypeName": "Diller", "Name": "Filipino" }, - { "TypeName": "Diller", "Name": "Arabic" }, - { "TypeName": "Diller", "Name": "Bengali" }, - { "TypeName": "Diller", "Name": "Mandarin Chinese" }, - { "TypeName": "Diller", "Name": "Wu Chinese" }, - { "TypeName": "Diller", "Name": "Hindi" }, - { "TypeName": "Diller", "Name": "Russian" }, - { "TypeName": "Diller", "Name": "Portuguese" }, - { "TypeName": "Diller", "Name": "Indonesian" }, - { "TypeName": "Diller", "Name": "Urdu" }, - { "TypeName": "Diller", "Name": "Japonca" }, - { "TypeName": "Diller", "Name": "Punjabi" }, - { "TypeName": "Diller", "Name": "Javanese" }, - { "TypeName": "Diller", "Name": "Telugu" }, - { "TypeName": "Diller", "Name": "Turkish" }, - { "TypeName": "Diller", "Name": "Korean" }, - { "TypeName": "Diller", "Name": "Marathi" }, - { "TypeName": "Soft Skills", "Name": "Adaptability" }, - { "TypeName": "Soft Skills", "Name": "Critical Thinking" }, - { "TypeName": "Soft Skills", "Name": "İletişim" } + { + "TypeName": "Diller", + "Name": "Spanish" + }, + { + "TypeName": "Diller", + "Name": "Fransızca" + }, + { + "TypeName": "Diller", + "Name": "English" + }, + { + "TypeName": "Diller", + "Name": "German" + }, + { + "TypeName": "Diller", + "Name": "Filipino" + }, + { + "TypeName": "Diller", + "Name": "Arabic" + }, + { + "TypeName": "Diller", + "Name": "Bengali" + }, + { + "TypeName": "Diller", + "Name": "Mandarin Chinese" + }, + { + "TypeName": "Diller", + "Name": "Wu Chinese" + }, + { + "TypeName": "Diller", + "Name": "Hindi" + }, + { + "TypeName": "Diller", + "Name": "Russian" + }, + { + "TypeName": "Diller", + "Name": "Portuguese" + }, + { + "TypeName": "Diller", + "Name": "Indonesian" + }, + { + "TypeName": "Diller", + "Name": "Urdu" + }, + { + "TypeName": "Diller", + "Name": "Japonca" + }, + { + "TypeName": "Diller", + "Name": "Punjabi" + }, + { + "TypeName": "Diller", + "Name": "Javanese" + }, + { + "TypeName": "Diller", + "Name": "Telugu" + }, + { + "TypeName": "Diller", + "Name": "Turkish" + }, + { + "TypeName": "Diller", + "Name": "Korean" + }, + { + "TypeName": "Diller", + "Name": "Marathi" + }, + { + "TypeName": "Soft Skills", + "Name": "Adaptability" + }, + { + "TypeName": "Soft Skills", + "Name": "Critical Thinking" + }, + { + "TypeName": "Soft Skills", + "Name": "İletişim" + } ], "SkillLevels": [ { @@ -19643,14 +20016,32 @@ } ], "ContactTags": [ - { "Name": "Yurtdışı", "Category": "" }, - { "Name": "Yurtiçi", "Category": "" } + { + "Name": "Yurtdışı", + "Category": "" + }, + { + "Name": "Yurtiçi", + "Category": "" + } ], "ContactTitles": [ - { "Title": "Bay", "Abbreviation": "Bay." }, - { "Title": "Bayan", "Abbreviation": "Bayan" }, - { "Title": "Doktora", "Abbreviation": "Dr." }, - { "Title": "Profesör", "Abbreviation": "Prof." } + { + "Title": "Bay", + "Abbreviation": "Bay." + }, + { + "Title": "Bayan", + "Abbreviation": "Bayan" + }, + { + "Title": "Doktora", + "Abbreviation": "Dr." + }, + { + "Title": "Profesör", + "Abbreviation": "Prof." + } ], "ForumCategories": [ { @@ -19685,5 +20076,122 @@ "Order": 4, "IsActive": true } + ], + "BlogCategories": [ + { + "Id": "1e97bf2c-dec8-50bb-af20-70e71d752871", + "Name": "blog.categories.technology", + "Slug": "ai-ve-gelecegi", + "Description": "blog.posts.ai.excerpt", + "DisplayOrder": 1 + }, + { + "Id": "6d0ae65d-8b91-5bbf-879d-87ee25410458", + "Name": "blog.categories.webdev", + "Slug": "web-gelistirmede-son-trendler", + "Description": "blog.posts.web.excerpt", + "DisplayOrder": 2 + }, + { + "Id": "e938e6e6-f355-5807-a7f7-f0d4fe368fc5", + "Name": "blog.categories.security", + "Slug": "siber-guvenlik-tehditleri-ve-korunma-yollari", + "Description": "blog.posts.security.excerpt", + "DisplayOrder": 3 + }, + { + "Id": "ebd1b9aa-14ce-5c0b-9514-a4cbd5a7cf94", + "Name": "blog.categories.mobile", + "Slug": "mobil-uygulama-gelistirmede-cross-platform-cozumler", + "Description": "blog.posts.mobile.excerpt", + "DisplayOrder": 4 + }, + { + "Id": "741ef542-0591-5472-9bf2-593047eb4122", + "Name": "blog.categories.database", + "Slug": "veritabani-yonetim-sistemleri-karsilastirmasi", + "Description": "blog.posts.database.excerpt", + "DisplayOrder": 5 + }, + { + "Id": "dbc8578c-1a99-594a-8997-bddd0eac8571", + "Name": "blog.categories.digital", + "Slug": "dijital-pazarlamada-veri-analizi", + "Description": "blog.posts.digital.excerpt", + "DisplayOrder": 6 + } + ], + "BlogPosts": [ + { + "Id": "1a79a36e-e062-4335-9ddf-0557c60f3ea9", + "TenantId": null, + "Title": "blog.posts.ai.title", + "Slug": "ai-ve-gelecegi", + "Content": "Yapay zeka günümüzün en hızlı gelişen teknolojilerinden biridir.\nAkıllı asistanlardan otonom araçlara kadar pek çok alanda kullanılmaktadır.\nEtik sorunlar da beraberinde gelmektedir.", + "ReadTime": "5 dk", + "Summary": "blog.posts.ai.excerpt", + "CoverImage": "https://images.pexels.com/photos/8386434/pexels-photo-8386434.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "1e97bf2c-dec8-50bb-af20-70e71d752871", + "AuthorId": "1668adf0-fd2a-5216-9834-6b6874ec2a05" + }, + { + "Id": "e7d6f581-60ba-44d4-be37-c5d13e5c2fda", + "TenantId": null, + "Title": "blog.posts.web.title", + "Slug": "web-gelistirmede-son-trendler", + "Content": "Web geliştirme dünyası sürekli bir değişim ve evrim içinde. Her yıl yeni teknolojiler, frameworkler ve yaklaşımlar ortaya çıkıyor, bu da web geliştiricilerinin sürekli öğrenmesini ve adapte olmasını gerektiriyor. 2024 yılı da bu açıdan farklı değil; heyecan verici yeni trendler ve gelişmeler web ekosistemini şekillendiriyor.\n\nBu yılın öne çıkan trendlerinden biri Serverless mimarilerin yükselişi. Geliştiricilerin sunucu yönetimiyle uğraşmadan uygulama geliştirmesine olanak tanıyan Serverless, maliyet etkinliği ve ölçeklenebilirlik gibi avantajlar sunuyor. Bir diğer önemli trend ise WebAssembly (Wasm). Web tarayıcılarında yüksek performanslı kod çalıştırmayı mümkün kılan Wasm, oyunlar, video düzenleyiciler ve CAD yazılımları gibi daha karmaşık uygulamaların web'e taşınmasını sağlıyor.\n\nMikro ön uçlar da büyük ölçekli web uygulamalarının yönetimini kolaylaştıran bir mimari desen olarak popülerlik kazanıyor. Bu yaklaşım, büyük ön uç projelerini daha küçük, bağımsız parçalara ayırarak farklı ekiplerin aynı proje üzerinde paralel çalışmasına imkan tanıyor. Bu blog yazısında, bu ve benzeri web geliştirme trendlerini daha detaylı inceleyerek, geleceğin web uygulamalarını şekillendiren teknolojilere derinlemesine bir bakış sunacağız.", + "ReadTime": "7 dk", + "Summary": "blog.posts.web.excerpt", + "CoverImage": "https://images.pexels.com/photos/11035471/pexels-photo-11035471.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "6d0ae65d-8b91-5bbf-879d-87ee25410458", + "AuthorId": "7df16a77-92ed-50e6-8749-ae34345c01b9" + }, + { + "Id": "54ac1095-0a95-467e-9f86-01efa8af136b", + "TenantId": null, + "Title": "blog.posts.security.title", + "Slug": "siber-guvenlik-tehditleri-ve-korunma-yollari", + "Content": "Dijitalleşmenin hızla artmasıyla birlikte, siber güvenlik tehditlerinin sayısı ve karmaşıklığı da eş zamanlı olarak artmaktadır. Artık sadece büyük şirketler değil, bireyler ve küçük işletmeler de siber saldırganların hedefi haline gelmiştir. Kişisel verilerin çalınması, kimlik avı (phishing) saldırıları, fidye yazılımları (ransomware) ve dağıtılmış hizmet engelleme (DDoS) saldırıları gibi tehditler, hem maddi hem de manevi zararlara yol açabilmektedir.\n\nBu tehditlere karşı korunmak, dijital dünyada güvende kalmak için hayati öneme sahiptir. Güçlü ve benzersiz şifreler kullanmak, iki faktörlü kimlik doğrulama yöntemlerini benimsemek ve yazılımlarımızı düzenli olarak güncellemek, alabileceğimiz temel önlemler arasındadır. Ayrıca, bilinmeyen kaynaklardan gelen e-postalara ve linklere karşı dikkatli olmak, halka açık Wi-Fi ağlarında hassas işlemler yapmaktan kaçınmak da önemlidir.\n\nBu blog yazısında, güncel siber güvenlik tehditlerini daha detaylı bir şekilde inceleyeceğiz. Her bir tehdit türünün nasıl çalıştığını, potansiyel etkilerini ve bu tehditlere karşı bireysel ve kurumsal düzeyde alınabilecek en etkili korunma yollarını ele alacağız. Siber güvenlik bilincini artırmak ve dijital varlıklarımızı korumak için pratik ipuçları sunarak, daha güvenli bir çevrimiçi deneyim için rehberlik edeceğiz.", + "ReadTime": "6 dk", + "Summary": "blog.posts.security.excerpt", + "CoverImage": "https://images.pexels.com/photos/5380642/pexels-photo-5380642.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "e938e6e6-f355-5807-a7f7-f0d4fe368fc5" + }, + { + "Id": "bdd7e679-dd6e-4014-be13-b344fec2f283", + "TenantId": null, + "Title": "blog.posts.mobile.title", + "Slug": "mobil-uygulama-gelistirmede-cross-platform-cozumler", + "Content": "Mobil uygulama geliştirme, günümüzün en dinamik ve talep gören yazılım alanlarından biridir. Akıllı telefonların yaygınlaşmasıyla birlikte, işletmeler ve bireyler mobil platformlarda yer almak için sürekli yeni uygulamalar geliştirmektedir. Geleneksel olarak, iOS ve Android gibi farklı mobil işletim sistemleri için ayrı ayrı native uygulamalar geliştirmek gerekiyordu, bu da zaman ve kaynak açısından maliyetli olabiliyordu.\n\nAncak son yıllarda, cross-platform (çapraz platform) mobil geliştirme frameworkleri popülerlik kazanmıştır. Bu frameworkler, geliştiricilerin tek bir kod tabanı kullanarak hem iOS hem de Android platformları için uygulama geliştirmesine olanak tanır. Bu yaklaşım, geliştirme sürecini hızlandırır, maliyetleri düşürür ve bakım kolaylığı sağlar. React Native, Flutter ve Xamarin gibi frameworkler, cross-platform geliştirme alanında öne çıkan çözümlerdir.\n\nBu blog yazısında, cross-platform mobil geliştirmenin avantajlarını ve dezavantajlarını detaylı bir şekilde inceleyeceğiz. Farklı frameworklerin özelliklerini, performanslarını ve hangi senaryolarda daha uygun olduklarını karşılaştıracağız. Mobil uygulama geliştirme projeniz için en doğru cross-platform çözümü seçmenize yardımcı olacak bilgiler sunarak, geliştirme sürecinizi daha verimli hale getirmeniz için rehberlik edeceğiz.", + "Summary": "blog.posts.mobile.excerpt", + "ReadTime": "4 dk", + "CoverImage": "https://images.pexels.com/photos/13017583/pexels-photo-13017583.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "ebd1b9aa-14ce-5c0b-9514-a4cbd5a7cf94", + "AuthorId": "c107a187-5e41-51e1-a5b3-5bf85c16b39e" + }, + { + "Id": "777a2bac-5651-43af-ae08-4753a2a1ea51", + "TenantId": null, + "Title": "blog.posts.database.title", + "Slug": "veritabani-yonetim-sistemleri-karsilastirmasi", + "Content": "Veritabanları, modern yazılım uygulamalarının kalbidir. Uygulamaların veriyi depolaması, yönetmesi ve erişmesi için güvenilir ve etkili bir veritabanı yönetim sistemi (VTYS) seçimi kritik öneme sahiptir. Piyasada birçok farklı türde VTYS bulunmaktadır ve her birinin kendine özgü avantajları ve dezavantajları vardır. Doğru VTYS seçimi, uygulamanın performansı, ölçeklenebilirliği, güvenliği ve geliştirme süreci üzerinde doğrudan bir etkiye sahiptir.\n\nİlişkisel veritabanları (RDBMS), veriyi tablolar halinde düzenler ve SQL (Yapısal Sorgu Dili) kullanarak verilere erişim sağlar. MySQL, PostgreSQL, Oracle ve SQL Server gibi sistemler bu kategoriye girer. RDBMS'ler, veri tutarlılığı ve karmaşık sorgular için güçlü yetenekler sunar. Ancak, büyük ölçekli ve yapısal olmayan verilerle çalışırken performans sorunları yaşanabilir.\n\nNoSQL (Not Only SQL) veritabanları ise daha esnek veri modelleri sunar ve genellikle büyük ölçekli, dağıtık sistemler ve hızlı veri erişimi gerektiren uygulamalar için tercih edilir. MongoDB (belge tabanlı), Cassandra (geniş sütunlu) ve Redis (anahtar-değer) gibi sistemler NoSQL kategorisine örnektir. NoSQL veritabanları, yatay ölçeklenebilirlik ve yüksek erişilebilirlik sağlama konusunda genellikle RDBMS'lerden daha iyidir. Bu blog yazısında, ilişkisel ve NoSQL veritabanı yönetim sistemlerini detaylı bir şekilde karşılaştırarak, farklı kullanım senaryolarına göre hangi VTYS'nin daha uygun olduğunu ele alacağız.", + "Summary": "blog.posts.database.excerpt", + "ReadTime": "8 dk", + "CoverImage": "https://images.pexels.com/photos/325229/pexels-photo-325229.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "741ef542-0591-5472-9bf2-593047eb4122", + "AuthorId": "8f49d028-69d0-5d2b-abc3-559b7dee180f" + }, + { + "Id": "3dfbb220-9a2d-49e4-835a-213f47c60939", + "TenantId": null, + "Title": "blog.posts.digital.title", + "Slug": "dijital-pazarlamada-veri-analizi", + "Content": "Dijital pazarlama dünyası, sürekli değişen algoritmalar, yeni platformlar ve gelişen tüketici davranışlarıyla dinamik bir yapıya sahiptir. Bu karmaşık ortamda başarılı olmak için, pazarlamacıların verilere dayalı kararlar alması ve stratejilerini sürekli olarak optimize etmesi gerekmektedir. İşte tam bu noktada veri analizi devreye girer. Dijital pazarlamada veri analizi, kampanyaların performansını ölçmek, müşteri davranışlarını anlamak, hedef kitleyi daha iyi tanımak ve pazarlama yatırımlarının geri dönüşünü (ROI) maksimize etmek için kritik bir araçtır.\n\nVeri analizi sayesinde, hangi pazarlama kanallarının en etkili olduğunu belirleyebilir, hangi içerik türlerinin daha fazla etkileşim aldığını anlayabilir ve müşteri yolculuğunun farklı aşamalarındaki performans darboğazlarını tespit edebiliriz. Google Analytics, Adobe Analytics gibi web analizi araçları, sosyal medya analiz platformları ve müşteri ilişkileri yönetimi (CRM) sistemleri, dijital pazarlamacılara değerli veriler sunar. Bu verilerin doğru bir şekilde toplanması, temizlenmesi, analiz edilmesi ve yorumlanması, daha bilinçli ve etkili pazarlama stratejileri oluşturmanın temelini oluşturur.\n\nBu blog yazısında, dijital pazarlamada veri analizinin neden bu kadar önemli olduğunu detaylı bir şekilde ele alacağız. Kullanılan temel metrikleri (örneğin, dönüşüm oranı, tıklama oranı, müşteri edinme maliyeti), farklı analiz yöntemlerini ve veri görselleştirme tekniklerini inceleyeceğiz. Ayrıca, veri analizinden elde edilen içgörüleri pazarlama stratejilerine nasıl entegre edebileceğinize dair pratik ipuçları sunarak, dijital pazarlama çabalarınızın etkinliğini artırmanız için rehberlik edeceğiz.", + "Summary": "blog.posts.digital.excerpt", + "ReadTime": "6 dk", + "CoverImage": "https://images.pexels.com/photos/7681091/pexels-photo-7681091.jpeg?auto=compress&cs=tinysrgb&w=1920", + "CategoryId": "dbc8578c-1a99-594a-8997-bddd0eac8571", + "AuthorId": "727ec3f0-75dd-54e2-8ae6-13d49727ff58" + } ] -} +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs index 6413f1cb..b73b75d9 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/SeederDto.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Kurs.Languages.Entities; using Kurs.Platform.Charts.Dto; using Kurs.Platform.Entities; @@ -32,6 +33,8 @@ public class SeederDto public List SkillLevels { get; set; } public List ContactTags { get; set; } public List ContactTitles { get; set; } + public List BlogCategories { get; set; } + public List BlogPosts { get; set; } } public class ChartsSeedDto @@ -194,5 +197,27 @@ public class ContactTagSeedDto public class ContactTitleSeedDto { public string Title { get; set; } - public string Abbreviation { get; set; } + public string Abbreviation { get; set; } +} + +public class BlogCategorySeedDto +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Slug { get; set; } + public string Description { get; set; } + public int DisplayOrder { get; set; } +} + +public class BlogPostSeedDto +{ + public Guid Id { get; set; } + public string Title { get; set; } + public string Slug { get; set; } + public string Content { get; set; } + public string ReadTime { get; set; } + public string Summary { get; set; } + public string CoverImage { get; set; } + public Guid CategoryId { get; set; } + public Guid AuthorId { get; set; } } \ No newline at end of file diff --git a/api/src/Kurs.Platform.Domain/Blog/BlogPost.cs b/api/src/Kurs.Platform.Domain/Blog/BlogPost.cs index 09c0a0d8..8e6c746f 100644 --- a/api/src/Kurs.Platform.Domain/Blog/BlogPost.cs +++ b/api/src/Kurs.Platform.Domain/Blog/BlogPost.cs @@ -14,6 +14,7 @@ namespace Kurs.Platform.Blog public string Content { get; set; } public string Summary { get; set; } public string CoverImage { get; set; } + public string ReadTime { get; set; } public Guid CategoryId { get; set; } public virtual BlogCategory Category { get; set; } @@ -44,6 +45,8 @@ namespace Kurs.Platform.Blog string slug, string content, string summary, + string readTime, + string coverImage, Guid categoryId, Guid authorId, Guid? tenantId = null) : base(id) @@ -52,6 +55,8 @@ namespace Kurs.Platform.Blog Slug = slug; Content = content; Summary = summary; + ReadTime = readTime; + CoverImage = coverImage; CategoryId = categoryId; AuthorId = authorId; TenantId = tenantId; diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.Designer.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.Designer.cs index d9689944..529b5d2b 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20250619131606_AddBlogForumEntities")] + [Migration("20250619205823_AddBlogForumEntities")] partial class AddBlogForumEntities { /// @@ -894,6 +894,9 @@ namespace Kurs.Platform.Migrations b.Property("PublishedAt") .HasColumnType("datetime2"); + b.Property("ReadTime") + .HasColumnType("nvarchar(max)"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.cs index 27a3f697..e12bb246 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619131606_AddBlogForumEntities.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250619205823_AddBlogForumEntities.cs @@ -79,6 +79,7 @@ namespace Kurs.Platform.Migrations Content = table.Column(type: "nvarchar(max)", nullable: false), Summary = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: false), CoverImage = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + ReadTime = table.Column(type: "nvarchar(max)", nullable: true), CategoryId = table.Column(type: "uniqueidentifier", nullable: false), AuthorId = table.Column(type: "uniqueidentifier", nullable: false), ViewCount = table.Column(type: "int", nullable: false), diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index bc27493a..766f8979 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -891,6 +891,9 @@ namespace Kurs.Platform.Migrations b.Property("PublishedAt") .HasColumnType("datetime2"); + b.Property("ReadTime") + .HasColumnType("nvarchar(max)"); + b.Property("Slug") .IsRequired() .HasMaxLength(256) diff --git a/company/src/pages/Blog.tsx b/company/src/pages/Blog.tsx index 6474b3df..26084149 100644 --- a/company/src/pages/Blog.tsx +++ b/company/src/pages/Blog.tsx @@ -130,7 +130,7 @@ const Blog = () => { : "bg-gray-200 text-gray-700 hover:bg-gray-300" }`} > - {category.name} ({category.postCount}) + { t(category.name)} ({category.postCount}) ))} @@ -158,20 +158,20 @@ const Blog = () => {
{post.title}
- {post.category.name} + {t(post.category.name)}

- {post.title} + {t(post.title)}

-

{post.summary}

+

{t(post.summary)}

{/* Tags */} {post.tags.length > 0 && ( @@ -203,11 +203,7 @@ const Blog = () => {
- {typeof post.content === "string" && - post.content.length > 0 - ? Math.ceil(post.content.length / 1000) - : "-"}{" "} - dk + {post.readTime}
diff --git a/company/src/pages/BlogDetail.tsx b/company/src/pages/BlogDetail.tsx index 78d9fce4..6a729cc8 100644 --- a/company/src/pages/BlogDetail.tsx +++ b/company/src/pages/BlogDetail.tsx @@ -1,66 +1,110 @@ -import React from 'react'; -import { Link, useParams } from 'react-router-dom'; // Link ve useParams'ı import et -import { useLanguage } from '../context/LanguageContext'; // useLanguage hook'unu import et -import { blogContent, BlogPostContent } from '../locales/blogContent'; // blogContent ve BlogPostContent interface'ini import et +import React, { useState, useEffect } from "react"; +import { Link, useParams } from "react-router-dom"; +import { useLanguage } from "../context/LanguageContext"; +import { BlogPost, blogService } from "../services/api/blog.service"; +import { format } from "date-fns"; +import { tr } from "date-fns/locale"; + +interface PostData { + image?: string; + author?: { + id: string; + name: string; + avatar?: string; + }; +} const BlogDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); - const { t, language } = useLanguage(); // useLanguage hook'unu kullan ve dil bilgisini al + const { t } = useLanguage(); + const [blogPost, setBlogPost] = useState(null); + const [postData, setPostData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - // Basit slug oluşturma fonksiyonu (Blog.tsx'teki ile aynı olmalı) - const createSlug = (title: string) => { - return title - .toLowerCase() - .replace(/ /g, '-') - .replace(/[^\w-]+/g, ''); - }; + useEffect(() => { + const fetchBlogPost = async () => { + setLoading(true); + setError(null); + try { + if (id) { + const response = await blogService.getPostBySlug(id); + setBlogPost(response); + setPostData({ + image: response.coverImage, + author: response.author, + }); + } else { + setError("Blog post ID is missing."); + } + } catch (error: any) { + setError(error.message || "Failed to fetch blog post."); + } finally { + setLoading(false); + } + }; - // URL'deki slug'a göre blog yazısını bul - // blogContent objesinden slug'a karşılık gelen blog yazısını ve mevcut dile göre içeriğini al - const postData = blogContent[id || '']; // id undefined olabilir, boş string ile kontrol et - const blogPost = postData ? postData[language as 'tr' | 'en'] : undefined; // Mevcut dile göre içeriği al + fetchBlogPost(); + }, [id]); - if (!blogPost) { + if (loading) { return (
-

{t('blog.notFound')}

{/* Çeviri kullan */} +

Loading...

+
+ ); + } + + if (error) { + return ( +
+

Error: {error}

+
+ ); + } + + if (!blogPost || !postData) { + return ( +
+

+ {t("blog.notFound")} +

); } return ( -
{/* py-16 yerine pt-32 pb-16 kullanıldı */} +
- - ← {t('blog.backToBlog')} {/* Geri dönüş butonu */} + + ← {t("blog.backToBlog")} - {/* Blog yazısı görseli */} {postData.image && ( {t(blogPost.title)} )} -

{t(blogPost.title)}

{/* Çeviri kullan */} - {/* Yazar, tarih, okuma süresi gibi bilgiler eklenebilir */} +

+ {t(blogPost.title)} +

- {/* */} {/* İkonlar eklenebilir */} - {postData.author} {/* Yazar bilgisi blogContent'ten alınıyor */} + {postData.author?.name}
- {/* */} - {t(blogPost.date)} {/* Çeviri kullan */} -
-
- {/* */} - {blogPost.readTime} + {blogPost.publishedAt && + format(new Date(blogPost.publishedAt), "dd MMM yyyy", { + locale: tr, + })}
-
{/* Tailwind Typography eklentisi kuruluysa kullanılabilir */} -

{t(blogPost.content)}

{/* Tam içeriği çevirerek göster */} - {/* Daha uzun içerik burada paragraflar halinde yer alabilir */} +
+

{blogPost.content}

diff --git a/company/src/services/api/blog.service.ts b/company/src/services/api/blog.service.ts index 51251f30..73bbc0bb 100644 --- a/company/src/services/api/blog.service.ts +++ b/company/src/services/api/blog.service.ts @@ -1,4 +1,4 @@ -import { apiClient } from './config'; +import { apiClient } from "./config"; export interface BlogPost { id: string; @@ -6,6 +6,7 @@ export interface BlogPost { slug: string; content?: string; summary: string; + readTime: string; coverImage?: string; author: { id: string; @@ -22,6 +23,7 @@ export interface BlogPost { likeCount: number; commentCount: number; isPublished: boolean; + isLiked?: boolean; publishedAt?: string; createdAt: string; updatedAt: string; @@ -54,6 +56,7 @@ export interface BlogComment { export interface CreateBlogPostRequest { title: string; + slug: string; content: string; summary: string; categoryId: string; @@ -75,7 +78,7 @@ export interface BlogListParams { tag?: string; search?: string; authorId?: string; - sortBy?: 'latest' | 'popular' | 'trending'; + sortBy?: "latest" | "popular" | "trending"; } export interface PaginatedResponse { @@ -87,23 +90,45 @@ export interface PaginatedResponse { } class BlogService { - async getPosts(params: BlogListParams = {}): Promise> { - const response = await apiClient.get>('/api/app/blog/posts', { params }); + async getPosts( + params: BlogListParams = {} + ): Promise> { + const response = await apiClient.get>( + "/api/app/blog/posts", + { params } + ); return response.data; } - async getPost(idOrSlug: string): Promise { - const response = await apiClient.get(`/api/app/blog/posts/${idOrSlug}`); + async getPostById(id: string): Promise { + const response = await apiClient.get(`/api/app/blog/posts/${id}`); + return response.data; + } + + async getPostBySlug(slug: string): Promise { + const response = await apiClient.get( + `/api/app/blog/post-by-slug`, + { params: { slug } } + ); return response.data; } async createPost(data: CreateBlogPostRequest): Promise { - const response = await apiClient.post('/api/app/blog/posts', data); + const response = await apiClient.post( + "/api/app/blog/posts", + data + ); return response.data; } - async updatePost(id: string, data: Partial): Promise { - const response = await apiClient.put(`/api/app/blog/posts/${id}`, data); + async updatePost( + id: string, + data: Partial + ): Promise { + const response = await apiClient.put( + `/api/app/blog/posts/${id}`, + data + ); return response.data; } @@ -111,18 +136,59 @@ class BlogService { await apiClient.delete(`/api/app/blog/posts/${id}`); } - async getCategories(): Promise { - const response = await apiClient.get('/api/app/blog/categories'); + async publishPost(id: string): Promise { + const response = await apiClient.post( + `/api/app/blog/posts/${id}/publish` + ); return response.data; } + async unpublishPost(id: string): Promise { + const response = await apiClient.post( + `/api/app/blog/posts/${id}/unpublish` + ); + return response.data; + } + + async incrementViewCount(id: string): Promise { + await apiClient.post(`/api/app/blog/posts/${id}/view`); + } + + async likePost(id: string): Promise { + await apiClient.post(`/api/app/blog/posts/${id}/like`); + } + + async unlikePost(id: string): Promise { + await apiClient.delete(`/api/app/blog/posts/${id}/like`); + } + + async getCategories(): Promise { + const response = await apiClient.get( + "/api/app/blog/categories" + ); + return response.data; + } + + async getTags(count = 20): Promise { + const response = await apiClient.get( + `/api/app/blog/tags?count=${count}` + ); + return response.data; + } + + // Opsiyonel - Yorum API'si mevcutsa kullanılır async getComments(postId: string): Promise { - const response = await apiClient.get(`/api/app/blog/posts/${postId}/comments`); + const response = await apiClient.get( + `/api/app/blog/posts/${postId}/comments` + ); return response.data; } async createComment(data: CreateCommentRequest): Promise { - const response = await apiClient.post('/api/app/blog/comments', data); + const response = await apiClient.post( + "/api/app/blog/comments", + data + ); return response.data; } @@ -130,25 +196,12 @@ class BlogService { await apiClient.delete(`/api/app/blog/comments/${id}`); } - async likePost(postId: string): Promise { - await apiClient.post(`/api/app/blog/posts/${postId}/like`); + async likeComment(id: string): Promise { + await apiClient.post(`/api/app/blog/comments/${id}/like`); } - async unlikePost(postId: string): Promise { - await apiClient.delete(`/api/app/blog/posts/${postId}/like`); - } - - async likeComment(commentId: string): Promise { - await apiClient.post(`/api/app/blog/comments/${commentId}/like`); - } - - async unlikeComment(commentId: string): Promise { - await apiClient.delete(`/api/app/blog/comments/${commentId}/like`); - } - - async getTags(): Promise { - const response = await apiClient.get('/api/app/blog/tags'); - return response.data; + async unlikeComment(id: string): Promise { + await apiClient.delete(`/api/app/blog/comments/${id}/like`); } } diff --git a/ui/dev-dist/sw.js b/ui/dev-dist/sw.js index 2e25c0fc..13e730ce 100644 --- a/ui/dev-dist/sw.js +++ b/ui/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.n85sh48g8go" + "revision": "0.6c62okhp6do" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/ui/src/services/blog.service.ts b/ui/src/services/blog.service.ts index 49a7bf08..1078aa5e 100644 --- a/ui/src/services/blog.service.ts +++ b/ui/src/services/blog.service.ts @@ -1,55 +1,55 @@ -import apiService from "@/services/api.service"; +import apiService from '@/services/api.service' export interface BlogPost { - id: string; - title: string; - slug: string; - content?: string; - summary: string; - coverImage?: string; + id: string + title: string + slug: string + content?: string + summary: string + coverImage?: string author: { - id: string; - name: string; - avatar?: string; - }; + id: string + name: string + avatar?: string + } category: { - id: string; - name: string; - slug: string; - }; - tags: string[]; - viewCount: number; - likeCount: number; - commentCount: number; - isPublished: boolean; - publishedAt?: string; - createdAt: string; - updatedAt: string; + id: string + name: string + slug: string + } + tags: string[] + viewCount: number + likeCount: number + commentCount: number + isPublished: boolean + publishedAt?: string + createdAt: string + updatedAt: string } export interface BlogCategory { - id: string; - name: string; - slug: string; - description?: string; - postCount: number; + id: string + name: string + slug: string + description?: string + postCount: number } export interface BlogComment { - id: string; - postId: string; - content: string; + id: string + postId: string + content: string author: { - id: string; - name: string; - avatar?: string; - }; - parentId?: string; - replies?: BlogComment[]; - likeCount: number; - isLiked?: boolean; - createdAt: string; - updatedAt: string; + id: string + name: string + avatar?: string + } + parentId?: string + replies?: BlogComment[] + likeCount: number + isLiked?: boolean + createdAt: string + updatedAt: string } export interface CreateUpdateBlogPostDto { @@ -72,27 +72,27 @@ export interface CreateUpdateBlogCategoryDto { } export interface CreateCommentDto { - postId: string; - content: string; - parentId?: string; + postId: string + content: string + parentId?: string } export interface BlogListParams { - page?: number; - pageSize?: number; - categoryId?: string; - tag?: string; - search?: string; - authorId?: string; - sortBy?: 'latest' | 'popular' | 'trending'; + page?: number + pageSize?: number + categoryId?: string + tag?: string + search?: string + authorId?: string + sortBy?: 'latest' | 'popular' | 'trending' } export interface PaginatedResponse { - items: T[]; - totalCount: number; - pageNumber: number; - pageSize: number; - totalPages: number; + items: T[] + totalCount: number + pageNumber: number + pageSize: number + totalPages: number } class BlogService { @@ -100,161 +100,169 @@ class BlogService { const response = await apiService.fetchData>({ url: '/api/app/blog/posts', method: 'GET', - params - }); + params, + }) + return response.data + } + + async getPostBySlug(slug: string): Promise { + const response = await apiService.fetchData({ + url : `/api/app/blog/posts/post-by-slug/${slug}`, + method: 'GET', + }) return response.data; } async getPost(idOrSlug: string): Promise { const response = await apiService.fetchData({ url: `/api/app/blog/posts/${idOrSlug}`, - method: 'GET' - }); - return response.data; + method: 'GET', + }) + return response.data } async createPost(data: CreateUpdateBlogPostDto): Promise { const response = await apiService.fetchData({ - url: '/api/app/blog/posts', + url: '/api/app/blog/post', method: 'POST', - data: data as any - }); - return response.data; + data: data as any, + }) + return response.data } async updatePost(id: string, data: CreateUpdateBlogPostDto): Promise { const response = await apiService.fetchData({ - url: `/api/app/blog/posts/${id}`, + url: `/api/app/blog/${id}/post`, method: 'PUT', - data: data as any - }); - return response.data; + data: data as any, + }) + return response.data } async deletePost(id: string): Promise { await apiService.fetchData({ - url: `/api/app/blog/posts/${id}`, - method: 'DELETE' - }); + url: `/api/app/blog/${id}/post`, + method: 'DELETE', + }) } async publishPost(id: string): Promise { const response = await apiService.fetchData({ - url: `/api/app/blog/posts/${id}/publish`, - method: 'POST' - }); - return response.data; + url: `/api/app/blog/${id}/publish-post`, + method: 'POST', + }) + return response.data } async unpublishPost(id: string): Promise { const response = await apiService.fetchData({ - url: `/api/app/blog/posts/${id}/unpublish`, - method: 'POST' - }); - return response.data; + url: `/api/app/blog/${id}/unpublish-post`, + method: 'POST', + }) + return response.data } async getCategories(): Promise { const response = await apiService.fetchData({ url: '/api/app/blog/categories', - method: 'GET' - }); - return response.data; + method: 'GET', + }) + return response.data } async getComments(postId: string): Promise { const response = await apiService.fetchData({ url: `/api/app/blog/posts/${postId}/comments`, - method: 'GET' - }); - return response.data; + method: 'GET', + }) + return response.data } async createComment(data: CreateCommentDto): Promise { const response = await apiService.fetchData({ url: '/api/app/blog/comments', method: 'POST', - data: data as any - }); - return response.data; + data: data as any, + }) + return response.data } async deleteComment(id: string): Promise { await apiService.fetchData({ url: `/api/app/blog/comments/${id}`, - method: 'DELETE' - }); + method: 'DELETE', + }) } async likePost(postId: string): Promise { await apiService.fetchData({ - url: `/api/app/blog/posts/${postId}/like`, - method: 'POST' - }); + url: `/api/app/blog/${postId}/like-post`, + method: 'POST', + }) } async unlikePost(postId: string): Promise { await apiService.fetchData({ - url: `/api/app/blog/posts/${postId}/like`, - method: 'DELETE' - }); + url: `/api/app/blog/${postId}/unlike-post`, + method: 'DELETE', + }) } async likeComment(commentId: string): Promise { await apiService.fetchData({ url: `/api/app/blog/comments/${commentId}/like`, - method: 'POST' - }); + method: 'POST', + }) } async unlikeComment(commentId: string): Promise { await apiService.fetchData({ url: `/api/app/blog/comments/${commentId}/like`, - method: 'DELETE' - }); + method: 'DELETE', + }) } async getTags(): Promise { const response = await apiService.fetchData({ url: '/api/app/blog/tags', - method: 'GET' - }); - return response.data; + method: 'GET', + }) + return response.data } // Category methods async getCategory(id: string): Promise { const response = await apiService.fetchData({ - url: `/api/app/blog/categories/${id}`, - method: 'GET' - }); - return response.data; + url: `/api/app/blog/${id}/category`, + method: 'GET', + }) + return response.data } async createCategory(data: CreateUpdateBlogCategoryDto): Promise { const response = await apiService.fetchData({ - url: '/api/app/blog/categories', + url: '/api/app/blog/category', method: 'POST', - data: data as any - }); - return response.data; + data: data as any, + }) + return response.data } async updateCategory(id: string, data: CreateUpdateBlogCategoryDto): Promise { const response = await apiService.fetchData({ - url: `/api/app/blog/categories/${id}`, + url: `/api/app/blog/${id}/category`, method: 'PUT', - data: data as any - }); - return response.data; + data: data as any, + }) + return response.data } async deleteCategory(id: string): Promise { await apiService.fetchData({ - url: `/api/app/blog/categories/${id}`, - method: 'DELETE' - }); + url: `/api/app/blog/${id}/category`, + method: 'DELETE', + }) } } -export const blogService = new BlogService(); +export const blogService = new BlogService() diff --git a/ui/src/views/blog/BlogManagement.tsx b/ui/src/views/blog/BlogManagement.tsx index 46055584..b24f0ec6 100644 --- a/ui/src/views/blog/BlogManagement.tsx +++ b/ui/src/views/blog/BlogManagement.tsx @@ -19,7 +19,7 @@ import { } from '@/services/blog.service' import { format } from 'date-fns' import { tr } from 'date-fns/locale' -import { Field, Form, Formik } from 'formik' +import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import toast from '@/components/ui/toast' import Notification from '@/components/ui/Notification' @@ -30,6 +30,8 @@ import Th from '@/components/ui/Table/Th' import THead from '@/components/ui/Table/THead' import TBody from '@/components/ui/Table/TBody' import Td from '@/components/ui/Table/Td' +import { SelectBoxOption } from '@/shared/types' +import { enumToList } from '@/utils/enumUtils' const validationSchema = Yup.object().shape({ title: Yup.string().required('Başlık gereklidir'), @@ -54,6 +56,10 @@ const BlogManagement = () => { const [categoryModalVisible, setCategoryModalVisible] = useState(false) const [editingPost, setEditingPost] = useState(null) const [editingCategory, setEditingCategory] = useState(null) + const categoryItems = categories?.map((cat) => ({ + value: cat.id, + label: cat.name, + })) useEffect(() => { loadData() @@ -212,7 +218,7 @@ const BlogManagement = () => { description: values.description, icon: values.icon, displayOrder: values.displayOrder, - isActive: values.isActive + isActive: values.isActive, } if (editingCategory) { @@ -271,7 +277,7 @@ const BlogManagement = () => { description: editingCategory.description || '', icon: '', displayOrder: 0, - isActive: true + isActive: true, } : { name: '', @@ -279,7 +285,7 @@ const BlogManagement = () => { description: '', icon: '', displayOrder: 0, - isActive: true + isActive: true, } return ( @@ -287,11 +293,11 @@ const BlogManagement = () => {

Blog Yönetimi

{activeTab === 'posts' ? ( - ) : ( - )} @@ -304,13 +310,13 @@ const BlogManagement = () => { className={`pb-2 px-1 ${activeTab === 'posts' ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-600'}`} onClick={() => setActiveTab('posts')} > - Blog Yazıları + Blog Yazıları
@@ -413,7 +419,11 @@ const BlogManagement = () => { {category.postCount}
-