From 7c1cd195ff70c9770e9d829eaab78917c870d2f9 Mon Sep 17 00:00:00 2001 From: adevopg Date: Mon, 13 Jul 2026 01:01:48 +0000 Subject: [PATCH] Foro: mover tema entre foros (moderadores) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/forum-write.ts: moveTopic (forum_id + moved=1). lib/forum.ts: listForumsForMove. - API /api/forum/moderate: acción 'move' con forumId, validando foro destino visible y distinto del actual. - TopicModBar: selector «Mover a» con los foros visibles (excl. el actual); tras mover, redirige al foro destino. - i18n es/en: Forum.moveTopic. Verificado: build OK, move 401 sin sesión, /forum 200. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../[locale]/forum/topic/[topicId]/page.tsx | 7 ++-- web-next/app/api/forum/moderate/route.ts | 17 ++++++++-- web-next/components/TopicModBar.tsx | 32 +++++++++++++++++-- web-next/lib/forum-write.ts | 5 +++ web-next/lib/forum.ts | 12 +++++++ web-next/messages/en.json | 3 +- web-next/messages/es.json | 3 +- 7 files changed, 69 insertions(+), 10 deletions(-) diff --git a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx index 3f9271b..2f5a5df 100644 --- a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx +++ b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx @@ -1,7 +1,7 @@ import { notFound } from 'next/navigation' import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' -import { getTopic, getPosts, countPosts } from '@/lib/forum' +import { getTopic, getPosts, countPosts, listForumsForMove } from '@/lib/forum' import { getSession } from '@/lib/session' import { forumIsModerator, canEditPost } from '@/lib/forum-perm' import { ReplyForm } from '@/components/ReplyForm' @@ -33,6 +33,7 @@ export default async function TopicPage({ const posts = await getPosts(id, (page - 1) * PER_PAGE, PER_PAGE) const session = await getSession() const isMod = await forumIsModerator(session) + const moveForums = isMod ? (await listForumsForMove()).filter((f) => f.id !== topic!.forum_id) : [] const canReply = Boolean(session.username) && (!topic!.locked || isMod) return ( @@ -48,7 +49,9 @@ export default async function TopicPage({ {topic!.name} - {isMod && } + {isMod && ( + + )}
{posts.map((post) => ( diff --git a/web-next/app/api/forum/moderate/route.ts b/web-next/app/api/forum/moderate/route.ts index f742eee..f395dec 100644 --- a/web-next/app/api/forum/moderate/route.ts +++ b/web-next/app/api/forum/moderate/route.ts @@ -1,9 +1,9 @@ import { getSession } from '@/lib/session' -import { getTopic } from '@/lib/forum' -import { setTopicFlag } from '@/lib/forum-write' +import { getTopic, getForum } from '@/lib/forum' +import { setTopicFlag, moveTopic } from '@/lib/forum-write' import { forumIsModerator } from '@/lib/forum-perm' -type Action = 'lock' | 'unlock' | 'sticky' | 'unsticky' | 'delete' +type Action = 'lock' | 'unlock' | 'sticky' | 'unsticky' | 'delete' | 'move' /** Moderación de temas: bloquear, fijar, borrar. Solo moderadores. */ export async function POST(request: Request) { @@ -36,6 +36,17 @@ export async function POST(request: Request) { case 'delete': await setTopicFlag(topicId, 'deleted', true) return Response.json({ success: true, forumId: topic.forum_id }) + case 'move': { + const newForumId = Number(b.forumId) + if (!newForumId || newForumId === topic.forum_id) { + return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 }) + } + if (!(await getForum(newForumId))) { + return Response.json({ success: false, error: 'invalidForum' }, { status: 400 }) + } + await moveTopic(topicId, newForumId) + return Response.json({ success: true }) + } default: return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 }) } diff --git a/web-next/components/TopicModBar.tsx b/web-next/components/TopicModBar.tsx index eb155af..d0240b1 100644 --- a/web-next/components/TopicModBar.tsx +++ b/web-next/components/TopicModBar.tsx @@ -8,16 +8,18 @@ export function TopicModBar({ topicId, locked, sticky, + moveForums = [], }: { topicId: number locked: boolean sticky: boolean + moveForums?: { id: number; name: string }[] }) { const t = useTranslations('Forum') const router = useRouter() const [busy, setBusy] = useState(false) - async function act(action: string, confirmMsg?: string) { + async function act(action: string, extra?: Record, confirmMsg?: string) { if (busy) return if (confirmMsg && !confirm(confirmMsg)) return setBusy(true) @@ -26,11 +28,13 @@ export function TopicModBar({ method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', - body: JSON.stringify({ topicId, action }), + body: JSON.stringify({ topicId, action, ...extra }), }) const data: { success?: boolean; forumId?: number } = await res.json() if (data.success && action === 'delete' && data.forumId) { router.push(`/forum/${data.forumId}`) + } else if (data.success && action === 'move') { + router.push(`/forum/${extra?.forumId}`) } else { router.refresh() } @@ -58,9 +62,31 @@ export function TopicModBar({ > {sticky ? t('unpin') : t('pin')} + {moveForums.length > 0 && ( + + )}