diff --git a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx
index 2f5a5df..83d60f7 100644
--- a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx
+++ b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx
@@ -25,14 +25,14 @@ export default async function TopicPage({
const t = await getTranslations('Forum')
const id = Number(topicId)
- const topic = await getTopic(id)
- if (!topic) notFound()
- const total = await countPosts(id)
- const totalPages = Math.max(1, Math.ceil(total / PER_PAGE))
- const page = Math.min(Math.max(1, Number((await searchParams).page) || 1), totalPages)
- const posts = await getPosts(id, (page - 1) * PER_PAGE, PER_PAGE)
const session = await getSession()
const isMod = await forumIsModerator(session)
+ const topic = await getTopic(id, isMod)
+ if (!topic) notFound()
+ const total = await countPosts(id, isMod)
+ const totalPages = Math.max(1, Math.ceil(total / PER_PAGE))
+ const page = Math.min(Math.max(1, Number((await searchParams).page) || 1), totalPages)
+ const posts = await getPosts(id, (page - 1) * PER_PAGE, PER_PAGE, isMod)
const moveForums = isMod ? (await listForumsForMove()).filter((f) => f.id !== topic!.forum_id) : []
const canReply = Boolean(session.username) && (!topic!.locked || isMod)
@@ -46,18 +46,28 @@ export default async function TopicPage({
{topic!.sticky && [{t('pinned')}]}
{topic!.locked && [{t('locked')}]}
+ {topic!.deleted && [{t('deletedMark')}]}
{topic!.name}
{isMod && (
-
+
)}
{posts.map((post) => (
-
+
-
{post.poster}
+
+ {post.poster}
+ {post.deleted && [{t('deletedMark')}]}
+
{post.time && (
{new Date(post.time).toLocaleString(locale)}
@@ -70,7 +80,7 @@ export default async function TopicPage({
dangerouslySetInnerHTML={{ __html: post.text }}
/>
{canEditPost(session, isMod, post.poster_id) && (
-
+
)}
))}
diff --git a/web-next/app/api/forum/moderate/route.ts b/web-next/app/api/forum/moderate/route.ts
index f395dec..8b4a7dd 100644
--- a/web-next/app/api/forum/moderate/route.ts
+++ b/web-next/app/api/forum/moderate/route.ts
@@ -3,7 +3,7 @@ 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' | 'move'
+type Action = 'lock' | 'unlock' | 'sticky' | 'unsticky' | 'delete' | 'restore' | 'move'
/** Moderación de temas: bloquear, fijar, borrar. Solo moderadores. */
export async function POST(request: Request) {
@@ -17,7 +17,7 @@ export async function POST(request: Request) {
const action = String(b.action ?? '') as Action
if (!topicId) return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
- const topic = await getTopic(topicId)
+ const topic = await getTopic(topicId, true) // incluye borrados (ya verificado que es mod)
if (!topic) return Response.json({ success: false, error: 'topicNotFound' }, { status: 404 })
switch (action) {
@@ -36,6 +36,9 @@ export async function POST(request: Request) {
case 'delete':
await setTopicFlag(topicId, 'deleted', true)
return Response.json({ success: true, forumId: topic.forum_id })
+ case 'restore':
+ await setTopicFlag(topicId, 'deleted', false)
+ break
case 'move': {
const newForumId = Number(b.forumId)
if (!newForumId || newForumId === topic.forum_id) {
diff --git a/web-next/app/api/forum/post/route.ts b/web-next/app/api/forum/post/route.ts
index a160520..49e1311 100644
--- a/web-next/app/api/forum/post/route.ts
+++ b/web-next/app/api/forum/post/route.ts
@@ -6,6 +6,21 @@ import { plainLength } from '@/lib/forum-sanitize'
const MIN_TEXT_LEN = 5
+/** Restaurar un post borrado. Solo moderadores. */
+export async function PUT(request: Request) {
+ const session = await getSession()
+ if (!session.accountId) return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 })
+ if (!(await forumIsModerator(session))) return Response.json({ success: false, error: 'forbidden' }, { status: 403 })
+
+ let b: Record = {}
+ try { b = await request.json() } catch { return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 }) }
+ const postId = Number(b.postId)
+ if (!postId) return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
+
+ await setPostDeleted(postId, false)
+ return Response.json({ success: true })
+}
+
/** Editar el texto de un post propio (o cualquiera si es moderador). */
export async function PATCH(request: Request) {
const session = await getSession()
diff --git a/web-next/components/PostActions.tsx b/web-next/components/PostActions.tsx
index d96de74..9f0e435 100644
--- a/web-next/components/PostActions.tsx
+++ b/web-next/components/PostActions.tsx
@@ -5,7 +5,15 @@ import { useTranslations } from 'next-intl'
import { useRouter } from '@/i18n/navigation'
import { RichTextArea } from './RichTextArea'
-export function PostActions({ postId, initialText }: { postId: number; initialText: string }) {
+export function PostActions({
+ postId,
+ initialText,
+ deleted = false,
+}: {
+ postId: number
+ initialText: string
+ deleted?: boolean
+}) {
const t = useTranslations('Forum')
const router = useRouter()
const [editing, setEditing] = useState(false)
@@ -38,6 +46,26 @@ export function PostActions({ postId, initialText }: { postId: number; initialTe
}
}
+ async function restore() {
+ if (busy) return
+ setBusy(true)
+ setError(null)
+ try {
+ const res = await fetch('/api/forum/post', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'same-origin',
+ body: JSON.stringify({ postId }),
+ })
+ if ((await res.json()).success) router.refresh()
+ else setError(t('genericError'))
+ } catch {
+ setError(t('genericError'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
async function remove() {
if (busy || !confirm(t('confirmDeletePost'))) return
setBusy(true)
@@ -80,6 +108,17 @@ export function PostActions({ postId, initialText }: { postId: number; initialTe
)
}
+ if (deleted) {
+ return (
+
+
+ {error && {error}}
+
+ )
+ }
+
return (