Foro: barra de acciones por publicación (me gusta, enlace, denuncia, marcador)

- Me gusta con contador y toggle (al volver a pulsar se retira).
- Copiar enlace (permalink al post con ancla #post-ID).
- Denunciar: modal con motivos (troleo, amenaza, spam, etc.) + detalle.
- Guardar en marcadores + popover de recordatorio (2h/mañana/3 días/más)
  y modal "Editar marcador" con nota y recordatorio (nota + remind_at en BD).
- Tablas acore_web: forum_post_like, forum_post_bookmark (+note,+remind_at), forum_post_report.
- APIs /api/forum/{like,bookmark,report,bookmark-reminder}, gated por sesión.
- Estilo oscuro+dorado de la web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 08:30:57 +00:00
parent aaf106ef55
commit a73fe4d8d5
10 changed files with 776 additions and 3 deletions
@@ -0,0 +1,23 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/session'
import { updateBookmark } from '@/lib/forum-social'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
/** Guarda nota + recordatorio del marcador (crea el marcador si no existía). */
export async function POST(req: NextRequest) {
const session = await getSession()
const accountId = session.accountId ?? 0
if (!accountId) return NextResponse.json({ ok: false }, { status: 401 })
let postId = 0, note = '', remindAt: number | null = null
try {
const b = await req.json()
postId = Number(b?.postId) || 0
note = String(b?.note || '')
remindAt = b?.remindAt ? Number(b.remindAt) : null
} catch { /* */ }
if (!postId) return NextResponse.json({ ok: false }, { status: 400 })
const ok = await updateBookmark(accountId, postId, note, remindAt)
return NextResponse.json({ ok, bookmarked: true, note, remindAt }, { status: ok ? 200 : 400 })
}
+17
View File
@@ -0,0 +1,17 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/session'
import { togglePostBookmark } from '@/lib/forum-social'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export async function POST(req: NextRequest) {
const session = await getSession()
const accountId = session.accountId ?? 0
if (!accountId) return NextResponse.json({ ok: false }, { status: 401 })
let postId = 0
try { postId = Number((await req.json())?.postId) || 0 } catch { /* */ }
if (!postId) return NextResponse.json({ ok: false }, { status: 400 })
const r = await togglePostBookmark(accountId, postId)
return NextResponse.json({ ok: true, ...r })
}
+17
View File
@@ -0,0 +1,17 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/session'
import { togglePostLike } from '@/lib/forum-social'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export async function POST(req: NextRequest) {
const session = await getSession()
const accountId = session.accountId ?? 0
if (!accountId) return NextResponse.json({ ok: false }, { status: 401 })
let postId = 0
try { postId = Number((await req.json())?.postId) || 0 } catch { /* */ }
if (!postId) return NextResponse.json({ ok: false }, { status: 400 })
const r = await togglePostLike(accountId, postId)
return NextResponse.json({ ok: true, ...r })
}
+23
View File
@@ -0,0 +1,23 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/session'
import { reportPost } from '@/lib/forum-social'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export async function POST(req: NextRequest) {
const session = await getSession()
const accountId = session.accountId ?? 0
if (!accountId) return NextResponse.json({ ok: false }, { status: 401 })
let postId = 0, reason = '', detail = ''
try {
const b = await req.json()
postId = Number(b?.postId) || 0
reason = String(b?.reason || '')
detail = String(b?.detail || '')
} catch { /* */ }
if (!postId || !reason) return NextResponse.json({ ok: false }, { status: 400 })
const now = Math.floor(Date.now() / 1000)
const ok = await reportPost(accountId, postId, reason, detail, now)
return NextResponse.json({ ok }, { status: ok ? 200 : 400 })
}