From 4252cbf0f5dea2478c3f3ef14aadb049f6c5c235 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?=
<76204082+iamsedatozturk@users.noreply.github.com>
Date: Tue, 24 Jun 2025 10:09:33 +0300
Subject: [PATCH] =?UTF-8?q?Forum=20de=C4=9Fi=C5=9Fiklikleri?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Forum/ForumAppService.cs | 122 ++++++++++++++----
.../Kurs.Platform.DbMigrator.csproj | 1 +
ui/dev-dist/sw.js | 2 +-
ui/src/services/forumService.ts | 64 ++++-----
ui/src/views/forum/Management.tsx | 8 +-
ui/src/views/forum/admin/AdminView.tsx | 110 ++++++++--------
.../admin/{AdminStats.tsx => Dashboard.tsx} | 0
ui/src/views/forum/admin/PostManagement.tsx | 33 +++--
ui/src/views/forum/useForumData.ts | 12 +-
9 files changed, 212 insertions(+), 140 deletions(-)
rename ui/src/views/forum/admin/{AdminStats.tsx => Dashboard.tsx} (100%)
diff --git a/api/src/Kurs.Platform.Application/Forum/ForumAppService.cs b/api/src/Kurs.Platform.Application/Forum/ForumAppService.cs
index 8e6159e6..7477039c 100644
--- a/api/src/Kurs.Platform.Application/Forum/ForumAppService.cs
+++ b/api/src/Kurs.Platform.Application/Forum/ForumAppService.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.Domain.Entities;
@@ -159,7 +158,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
input.Slug,
input.Description,
input.Icon,
- input.DisplayOrder
+ input.DisplayOrder,
+ input.TenantId
)
{
IsActive = input.IsActive,
@@ -192,15 +192,17 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var category = await _categoryRepository.GetAsync(id);
category.IsLocked = !category.IsLocked;
+
await _categoryRepository.UpdateAsync(category);
return ObjectMapper.Map(category);
}
- [Authorize("App.ForumManagement.Update")]
+ [Authorize("App.ForumManagement.Update")]
public async Task UpdateCategoryActiveAsync(Guid id)
{
var category = await _categoryRepository.GetAsync(id);
category.IsActive = !category.IsActive;
+
await _categoryRepository.UpdateAsync(category);
return ObjectMapper.Map(category);
}
@@ -281,7 +283,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
input.Content,
input.CategoryId,
CurrentUser.Id.Value,
- CurrentUser.Name
+ CurrentUser.Name,
+ input.TenantId
)
{
IsPinned = input.IsPinned,
@@ -380,7 +383,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
input.Content,
CurrentUser.Id.Value,
CurrentUser.Name,
- input.ParentPostId
+ input.ParentPostId,
+ input.TenantId
);
await _postRepository.InsertAsync(post, autoSave: true);
@@ -473,24 +477,6 @@ public class ForumAppService : PlatformAppService, IForumAppService
await _categoryRepository.UpdateAsync(category);
}
- // Like/Unlike topic
- public async Task LikeTopicAsync(Guid id)
- {
- var topic = await _topicRepository.GetAsync(id);
- topic.LikeCount++;
- await _topicRepository.UpdateAsync(topic);
- return ObjectMapper.Map(topic);
- }
-
- public async Task UnlikeTopicAsync(Guid id)
- {
- var topic = await _topicRepository.GetAsync(id);
- topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
- await _topicRepository.UpdateAsync(topic);
- return ObjectMapper.Map(topic);
- }
-
- // Like/Unlike posts
public async Task LikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
@@ -523,6 +509,96 @@ public class ForumAppService : PlatformAppService, IForumAppService
return ObjectMapper.Map(post);
}
+ public async Task MarkPostAsync(Guid id)
+ {
+ var post = await _postRepository.GetAsync(id);
+ post.IsAcceptedAnswer = true;
+
+ await _postRepository.UpdateAsync(post);
+
+ return ObjectMapper.Map(post);
+ }
+
+ public async Task UnmarkPostAsync(Guid id)
+ {
+ var post = await _postRepository.GetAsync(id);
+ post.IsAcceptedAnswer = false;
+ await _postRepository.UpdateAsync(post);
+
+ return ObjectMapper.Map(post);
+ }
+
+ // Like/Unlike topic
+ public async Task LikeTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.LikeCount++;
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task UnlikeTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task PinTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsPinned = true;
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task UnpinTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsPinned = false;
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task LockTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsLocked = true;
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task UnlockTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsLocked = false;
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task SolvedTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsSolved = true;
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
+ public async Task UnsolvedTopicAsync(Guid id)
+ {
+ var topic = await _topicRepository.GetAsync(id);
+ topic.IsSolved = false;
+
+ await _topicRepository.UpdateAsync(topic);
+ return ObjectMapper.Map(topic);
+ }
+
// Statistics
public async Task GetForumStatsAsync()
{
diff --git a/api/src/Kurs.Platform.DbMigrator/Kurs.Platform.DbMigrator.csproj b/api/src/Kurs.Platform.DbMigrator/Kurs.Platform.DbMigrator.csproj
index eeefebb6..b09a0b47 100644
--- a/api/src/Kurs.Platform.DbMigrator/Kurs.Platform.DbMigrator.csproj
+++ b/api/src/Kurs.Platform.DbMigrator/Kurs.Platform.DbMigrator.csproj
@@ -5,6 +5,7 @@
Exe
net9.0
+ 467bbc0f-83d0-40d0-a9f2-230c8620bdad
diff --git a/ui/dev-dist/sw.js b/ui/dev-dist/sw.js
index 71fbc8e3..0b99fe8b 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.mvu82hb2mqg"
+ "revision": "0.m83v8d1s4bo"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
diff --git a/ui/src/services/forumService.ts b/ui/src/services/forumService.ts
index 5ffb3dce..9c47d535 100644
--- a/ui/src/services/forumService.ts
+++ b/ui/src/services/forumService.ts
@@ -159,14 +159,6 @@ class ForumService {
})
}
- // async toggleCategoryStatus(id: string): Promise {
- // const response = await apiService.fetchData({
- // url: `/api/app/forum/categories/${id}/toggle-status`,
- // method: 'POST',
- // })
- // return response.data
- // }
-
// Topics
async getTopics(params: TopicListParams = {}): Promise> {
const response = await apiService.fetchData>({
@@ -210,9 +202,25 @@ class ForumService {
})
}
+ async likeTopic(id: string): Promise {
+ const response = await apiService.fetchData({
+ url: `/api/app/forum/${id}/like-topic`,
+ method: 'POST',
+ })
+ return response.data
+ }
+
+ async unlikeTopic(id: string): Promise {
+ const response = await apiService.fetchData({
+ url: `/api/app/forum/${id}/unlike-topic`,
+ method: 'POST',
+ })
+ return response.data
+ }
+
async pinTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/pin`,
+ url: `/api/app/forum/${id}/pin-topic`,
method: 'POST',
})
return response.data
@@ -220,7 +228,7 @@ class ForumService {
async unpinTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/unpin`,
+ url: `/api/app/forum/${id}/unpin-topic`,
method: 'POST',
})
return response.data
@@ -228,7 +236,7 @@ class ForumService {
async lockTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/lock`,
+ url: `/api/app/forum/${id}/lock-topic`,
method: 'POST',
})
return response.data
@@ -236,23 +244,23 @@ class ForumService {
async unlockTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/unlock`,
+ url: `/api/app/forum/${id}/unlock-topic`,
method: 'POST',
})
return response.data
}
- async markTopicAsSolved(id: string): Promise {
+ async solvedTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/mark-solved`,
+ url: `/api/app/forum/${id}/solved-topic`,
method: 'POST',
})
return response.data
}
- async markTopicAsUnsolved(id: string): Promise {
+ async unsolvedTopic(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/topics/${id}/mark-unsolved`,
+ url: `/api/app/forum/${id}/unsolved-topic`,
method: 'POST',
})
return response.data
@@ -287,7 +295,7 @@ class ForumService {
async updatePost(id: string, data: Partial): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/posts/${id}`,
+ url: `/api/app/forum/${id}/post`,
method: 'PUT',
data,
})
@@ -296,7 +304,7 @@ class ForumService {
async deletePost(id: string): Promise {
await apiService.fetchData({
- url: `/api/app/forum/posts/${id}`,
+ url: `/api/app/forum/${id}/post`,
method: 'DELETE',
})
}
@@ -317,25 +325,9 @@ class ForumService {
return response.data
}
- async likeTopic(id: string): Promise {
- const response = await apiService.fetchData({
- url: `/api/app/forum/${id}/like-topic`,
- method: 'POST',
- })
- return response.data
- }
-
- async unlikeTopic(id: string): Promise {
- const response = await apiService.fetchData({
- url: `/api/app/forum/${id}/unlike-topic`,
- method: 'POST',
- })
- return response.data
- }
-
async markPostAsAcceptedAnswer(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/posts/${id}/mark-accepted`,
+ url: `/api/app/forum/${id}/mark-post`,
method: 'POST',
})
return response.data
@@ -343,7 +335,7 @@ class ForumService {
async unmarkPostAsAcceptedAnswer(id: string): Promise {
const response = await apiService.fetchData({
- url: `/api/app/forum/posts/${id}/unmark-accepted`,
+ url: `/api/app/forum/${id}/unmark-post`,
method: 'POST',
})
return response.data
diff --git a/ui/src/views/forum/Management.tsx b/ui/src/views/forum/Management.tsx
index 002ab9ed..4c00e1f4 100644
--- a/ui/src/views/forum/Management.tsx
+++ b/ui/src/views/forum/Management.tsx
@@ -24,8 +24,8 @@ export function Management() {
unpinTopic,
lockTopic,
unlockTopic,
- markTopicAsSolved,
- markTopicAsUnsolved,
+ solvedTopic,
+ unsolvedTopic,
createPost,
updatePost,
deletePost,
@@ -91,8 +91,8 @@ export function Management() {
onUnpinTopic={(id) => unpinTopic(id).then(() => {})}
onLockTopic={(id) => lockTopic(id).then(() => {})}
onUnlockTopic={(id) => unlockTopic(id).then(() => {})}
- onMarkTopicAsSolved={(id) => markTopicAsSolved(id).then(() => {})}
- onMarkTopicAsUnsolved={(id) => markTopicAsUnsolved(id).then(() => {})}
+ onMarkTopicAsSolved={(id) => solvedTopic(id).then(() => {})}
+ onMarkTopicAsUnsolved={(id) => unsolvedTopic(id).then(() => {})}
onCreatePost={(data) => createPost(data).then(() => {})}
onUpdatePost={(id, data) => updatePost(id, data).then(() => {})}
onDeletePost={(id) => deletePost(id).then(() => {})}
diff --git a/ui/src/views/forum/admin/AdminView.tsx b/ui/src/views/forum/admin/AdminView.tsx
index eec8770a..1ccbcc66 100644
--- a/ui/src/views/forum/admin/AdminView.tsx
+++ b/ui/src/views/forum/admin/AdminView.tsx
@@ -1,56 +1,52 @@
-import React, { useState } from 'react';
-import { Folder, MessageSquare, FileText, Plus, BarChart3 } from 'lucide-react';
-import { CategoryManagement } from './CategoryManagement';
-import { TopicManagement } from './TopicManagement';
-import { PostManagement } from './PostManagement';
-import { AdminStats } from './AdminStats';
-import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum';
+import React, { useState } from 'react'
+import { Folder, MessageSquare, FileText, Plus, BarChart3 } from 'lucide-react'
+import { CategoryManagement } from './CategoryManagement'
+import { TopicManagement } from './TopicManagement'
+import { PostManagement } from './PostManagement'
+import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
+import { AdminStats } from './Dashboard'
interface AdminViewProps {
- categories: ForumCategory[];
- topics: ForumTopic[];
- posts: ForumPost[];
- loading: boolean;
+ categories: ForumCategory[]
+ topics: ForumTopic[]
+ posts: ForumPost[]
+ loading: boolean
onCreateCategory: (category: {
- name: string;
- slug: string;
- description: string;
- icon: string;
- displayOrder: number;
- isActive: boolean;
- isLocked: boolean;
- }) => Promise;
- onUpdateCategory: (id: string, category: Partial) => Promise;
- onUpdateCategoryLockState: (id: string) => Promise;
- onUpdateCategoryActiveState: (id: string) => Promise;
- onDeleteCategory: (id: string) => Promise;
+ name: string
+ slug: string
+ description: string
+ icon: string
+ displayOrder: number
+ isActive: boolean
+ isLocked: boolean
+ }) => Promise
+ onUpdateCategory: (id: string, category: Partial) => Promise
+ onUpdateCategoryLockState: (id: string) => Promise
+ onUpdateCategoryActiveState: (id: string) => Promise
+ onDeleteCategory: (id: string) => Promise
onCreateTopic: (topic: {
- title: string;
- content: string;
- categoryId: string;
- isPinned?: boolean;
- isLocked?: boolean;
- }) => Promise;
- onUpdateTopic: (id: string, topic: Partial) => Promise;
- onDeleteTopic: (id: string) => Promise;
- onPinTopic: (id: string) => Promise;
- onUnpinTopic: (id: string) => Promise;
- onLockTopic: (id: string) => Promise;
- onUnlockTopic: (id: string) => Promise;
- onMarkTopicAsSolved: (id: string) => Promise;
- onMarkTopicAsUnsolved: (id: string) => Promise;
- onCreatePost: (post: {
- topicId: string;
- content: string;
- parentPostId?: string;
- }) => Promise;
- onUpdatePost: (id: string, post: Partial) => Promise;
- onDeletePost: (id: string) => Promise;
- onMarkPostAsAcceptedAnswer: (id: string) => Promise;
- onUnmarkPostAsAcceptedAnswer: (id: string) => Promise;
+ title: string
+ content: string
+ categoryId: string
+ isPinned?: boolean
+ isLocked?: boolean
+ }) => Promise
+ onUpdateTopic: (id: string, topic: Partial) => Promise
+ onDeleteTopic: (id: string) => Promise
+ onPinTopic: (id: string) => Promise
+ onUnpinTopic: (id: string) => Promise
+ onLockTopic: (id: string) => Promise
+ onUnlockTopic: (id: string) => Promise
+ onMarkTopicAsSolved: (id: string) => Promise
+ onMarkTopicAsUnsolved: (id: string) => Promise
+ onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise
+ onUpdatePost: (id: string, post: Partial) => Promise
+ onDeletePost: (id: string) => Promise
+ onMarkPostAsAcceptedAnswer: (id: string) => Promise
+ onUnmarkPostAsAcceptedAnswer: (id: string) => Promise
}
-type AdminSection = 'stats' | 'categories' | 'topics' | 'posts';
+type AdminSection = 'stats' | 'categories' | 'topics' | 'posts'
export function AdminView({
categories,
@@ -75,16 +71,16 @@ export function AdminView({
onUpdatePost,
onDeletePost,
onMarkPostAsAcceptedAnswer,
- onUnmarkPostAsAcceptedAnswer
+ onUnmarkPostAsAcceptedAnswer,
}: AdminViewProps) {
- const [activeSection, setActiveSection] = useState('stats');
+ const [activeSection, setActiveSection] = useState('stats')
const navigationItems = [
{ id: 'stats' as AdminSection, label: 'Dashboard', icon: BarChart3 },
{ id: 'categories' as AdminSection, label: 'Categories', icon: Folder },
{ id: 'topics' as AdminSection, label: 'Topics', icon: MessageSquare },
{ id: 'posts' as AdminSection, label: 'Posts', icon: FileText },
- ];
+ ]
return (
@@ -93,7 +89,7 @@ export function AdminView({
@@ -117,7 +113,7 @@ export function AdminView({
{activeSection === 'stats' && (
)}
-
+
{activeSection === 'categories' && (
)}
-
+
{activeSection === 'topics' && (
)}
-
+
{activeSection === 'posts' && (
- );
-}
\ No newline at end of file
+ )
+}
diff --git a/ui/src/views/forum/admin/AdminStats.tsx b/ui/src/views/forum/admin/Dashboard.tsx
similarity index 100%
rename from ui/src/views/forum/admin/AdminStats.tsx
rename to ui/src/views/forum/admin/Dashboard.tsx
diff --git a/ui/src/views/forum/admin/PostManagement.tsx b/ui/src/views/forum/admin/PostManagement.tsx
index 4200b9b8..ae92f185 100644
--- a/ui/src/views/forum/admin/PostManagement.tsx
+++ b/ui/src/views/forum/admin/PostManagement.tsx
@@ -1,6 +1,8 @@
import React, { useState } from 'react'
import { Plus, Edit2, Trash2, CheckCircle, Circle, Heart, Loader2 } from 'lucide-react'
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
+import ReactQuill from 'react-quill'
+import 'react-quill/dist/quill.snow.css'
interface PostManagementProps {
posts: ForumPost[]
@@ -23,11 +25,11 @@ export function PostManagement({
onMarkPostAsAcceptedAnswer,
onUnmarkPostAsAcceptedAnswer,
}: PostManagementProps) {
+ const [content, setContent] = useState('')
const [showCreateForm, setShowCreateForm] = useState(false)
const [editingPost, setEditingPost] = useState(null)
const [formData, setFormData] = useState({
topicId: '',
- content: '',
isAcceptedAnswer: false,
})
const [submitting, setSubmitting] = useState(false)
@@ -35,7 +37,6 @@ export function PostManagement({
const resetForm = () => {
setFormData({
topicId: '',
- content: '',
isAcceptedAnswer: false,
})
setShowCreateForm(false)
@@ -44,15 +45,18 @@ export function PostManagement({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
- if (submitting) return
+ if (submitting || !content.trim()) return
try {
setSubmitting(true)
+ const data = { ...formData, content: content.trim() }
+
if (editingPost) {
- await onUpdatePost(editingPost.id, formData)
+ await onUpdatePost(editingPost.id, data)
} else {
- await onCreatePost(formData)
+ await onCreatePost(data)
}
+
resetForm()
} catch (error) {
console.error('Error submitting form:', error)
@@ -65,9 +69,9 @@ export function PostManagement({
setEditingPost(post)
setFormData({
topicId: post.topicId,
- content: post.content,
isAcceptedAnswer: post.isAcceptedAnswer,
})
+ setContent(post.content)
setShowCreateForm(true)
}
@@ -151,12 +155,12 @@ export function PostManagement({
-
@@ -230,7 +234,10 @@ export function PostManagement({
Reply to:{' '}
{getTopicTitle(post.topicId)}
- {post.content}
+
diff --git a/ui/src/views/forum/useForumData.ts b/ui/src/views/forum/useForumData.ts
index a7f4bd2d..e0e61d62 100644
--- a/ui/src/views/forum/useForumData.ts
+++ b/ui/src/views/forum/useForumData.ts
@@ -248,9 +248,9 @@ export function useForumData() {
}
}
- const markTopicAsSolved = async (id: string) => {
+ const solvedTopic = async (id: string) => {
try {
- const updatedTopic = await forumService.markTopicAsSolved(id)
+ const updatedTopic = await forumService.solvedTopic(id)
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
return updatedTopic
} catch (err) {
@@ -260,9 +260,9 @@ export function useForumData() {
}
}
- const markTopicAsUnsolved = async (id: string) => {
+ const unsolvedTopic = async (id: string) => {
try {
- const updatedTopic = await forumService.markTopicAsUnsolved(id)
+ const updatedTopic = await forumService.unsolvedTopic(id)
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
return updatedTopic
} catch (err) {
@@ -432,8 +432,8 @@ export function useForumData() {
unpinTopic,
lockTopic,
unlockTopic,
- markTopicAsSolved,
- markTopicAsUnsolved,
+ solvedTopic,
+ unsolvedTopic,
// Post operations
createPost,