Forum değişiklikleri

This commit is contained in:
Sedat ÖZTÜRK 2025-06-24 10:09:33 +03:00
parent b0311425ce
commit 4252cbf0f5
9 changed files with 212 additions and 140 deletions

View file

@ -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,6 +192,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var category = await _categoryRepository.GetAsync(id);
category.IsLocked = !category.IsLocked;
await _categoryRepository.UpdateAsync(category);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
@ -201,6 +202,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var category = await _categoryRepository.GetAsync(id);
category.IsActive = !category.IsActive;
await _categoryRepository.UpdateAsync(category);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(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<ForumTopicDto> LikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount++;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnlikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
// Like/Unlike posts
public async Task<ForumPostDto> LikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
@ -523,6 +509,96 @@ public class ForumAppService : PlatformAppService, IForumAppService
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
public async Task<ForumPostDto> MarkPostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = true;
await _postRepository.UpdateAsync(post);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
public async Task<ForumPostDto> UnmarkPostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = false;
await _postRepository.UpdateAsync(post);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
// Like/Unlike topic
public async Task<ForumTopicDto> LikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount++;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnlikeTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> PinTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = true;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnpinTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = false;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> LockTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = true;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnlockTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = false;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> SolvedTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = true;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
public async Task<ForumTopicDto> UnsolvedTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = false;
await _topicRepository.UpdateAsync(topic);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
// Statistics
public async Task<ForumStatsDto> GetForumStatsAsync()
{

View file

@ -5,6 +5,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<UserSecretsId>467bbc0f-83d0-40d0-a9f2-230c8620bdad</UserSecretsId>
</PropertyGroup>
<ItemGroup>

View file

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

View file

@ -159,14 +159,6 @@ class ForumService {
})
}
// async toggleCategoryStatus(id: string): Promise<ForumCategory> {
// const response = await apiService.fetchData<ForumCategory>({
// url: `/api/app/forum/categories/${id}/toggle-status`,
// method: 'POST',
// })
// return response.data
// }
// Topics
async getTopics(params: TopicListParams = {}): Promise<PaginatedResponse<ForumTopic>> {
const response = await apiService.fetchData<PaginatedResponse<ForumTopic>>({
@ -210,9 +202,25 @@ class ForumService {
})
}
async likeTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
url: `/api/app/forum/${id}/like-topic`,
method: 'POST',
})
return response.data
}
async unlikeTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
url: `/api/app/forum/${id}/unlike-topic`,
method: 'POST',
})
return response.data
}
async pinTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
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<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
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<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
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<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
url: `/api/app/forum/topics/${id}/unlock`,
url: `/api/app/forum/${id}/unlock-topic`,
method: 'POST',
})
return response.data
}
async markTopicAsSolved(id: string): Promise<ForumTopic> {
async solvedTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
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<ForumTopic> {
async unsolvedTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
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<CreatePostRequest>): Promise<ForumPost> {
const response = await apiService.fetchData<ForumPost>({
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<void> {
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<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
url: `/api/app/forum/${id}/like-topic`,
method: 'POST',
})
return response.data
}
async unlikeTopic(id: string): Promise<ForumTopic> {
const response = await apiService.fetchData<ForumTopic>({
url: `/api/app/forum/${id}/unlike-topic`,
method: 'POST',
})
return response.data
}
async markPostAsAcceptedAnswer(id: string): Promise<ForumPost> {
const response = await apiService.fetchData<ForumPost>({
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<ForumPost> {
const response = await apiService.fetchData<ForumPost>({
url: `/api/app/forum/posts/${id}/unmark-accepted`,
url: `/api/app/forum/${id}/unmark-post`,
method: 'POST',
})
return response.data

View file

@ -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(() => {})}

View file

@ -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<void>;
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>;
onUpdateCategoryLockState: (id: string) => Promise<void>;
onUpdateCategoryActiveState: (id: string) => Promise<void>;
onDeleteCategory: (id: string) => Promise<void>;
name: string
slug: string
description: string
icon: string
displayOrder: number
isActive: boolean
isLocked: boolean
}) => Promise<void>
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>
onUpdateCategoryLockState: (id: string) => Promise<void>
onUpdateCategoryActiveState: (id: string) => Promise<void>
onDeleteCategory: (id: string) => Promise<void>
onCreateTopic: (topic: {
title: string;
content: string;
categoryId: string;
isPinned?: boolean;
isLocked?: boolean;
}) => Promise<void>;
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>;
onDeleteTopic: (id: string) => Promise<void>;
onPinTopic: (id: string) => Promise<void>;
onUnpinTopic: (id: string) => Promise<void>;
onLockTopic: (id: string) => Promise<void>;
onUnlockTopic: (id: string) => Promise<void>;
onMarkTopicAsSolved: (id: string) => Promise<void>;
onMarkTopicAsUnsolved: (id: string) => Promise<void>;
onCreatePost: (post: {
topicId: string;
content: string;
parentPostId?: string;
}) => Promise<void>;
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>;
onDeletePost: (id: string) => Promise<void>;
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>;
onUnmarkPostAsAcceptedAnswer: (id: string) => Promise<void>;
title: string
content: string
categoryId: string
isPinned?: boolean
isLocked?: boolean
}) => Promise<void>
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>
onDeleteTopic: (id: string) => Promise<void>
onPinTopic: (id: string) => Promise<void>
onUnpinTopic: (id: string) => Promise<void>
onLockTopic: (id: string) => Promise<void>
onUnlockTopic: (id: string) => Promise<void>
onMarkTopicAsSolved: (id: string) => Promise<void>
onMarkTopicAsUnsolved: (id: string) => Promise<void>
onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise<void>
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>
onDeletePost: (id: string) => Promise<void>
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>
onUnmarkPostAsAcceptedAnswer: (id: string) => Promise<void>
}
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<AdminSection>('stats');
const [activeSection, setActiveSection] = useState<AdminSection>('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 (
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
@ -93,7 +89,7 @@ export function AdminView({
<div className="lg:w-64 flex-shrink-0">
<nav className="space-y-2">
{navigationItems.map((item) => {
const Icon = item.icon;
const Icon = item.icon
return (
<button
key={item.id}
@ -107,7 +103,7 @@ export function AdminView({
<Icon className="w-5 h-5" />
<span className="font-medium">{item.label}</span>
</button>
);
)
})}
</nav>
</div>
@ -162,5 +158,5 @@ export function AdminView({
</div>
</div>
</div>
);
)
}

View file

@ -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<ForumPost | null>(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({
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Content</label>
<textarea
value={formData.content}
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
rows={6}
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
<ReactQuill
theme="snow"
value={content}
onChange={setContent}
style={{ height: '400px', marginBottom: '50px' }}
placeholder={'Write your message...'}
/>
</div>
@ -230,7 +234,10 @@ export function PostManagement({
Reply to:{' '}
<span className="font-medium">{getTopicTitle(post.topicId)}</span>
</p>
<p className="text-gray-700 line-clamp-3">{post.content}</p>
<p
className="text-gray-700 line-clamp-3"
dangerouslySetInnerHTML={{ __html: post.content }}
></p>
</div>
<div className="flex items-center justify-between text-sm text-gray-500">

View file

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