8bb18838ff
- lib/admin-forum.ts: listCategoriesWithForums, createCategory/deleteCategory (solo si vacía), createForum/deleteForum (borrado lógico), setForumVisibility. - API: /api/admin/forum(+/[id] DELETE/PATCH visibilidad) y /api/admin/forum/category(+/[id]) (403 sin admin). - UI: /admin/forum (AdminForumManager) — alta de categorías, alta/borrado de foros por categoría, mostrar/ocultar; tarjeta "Gestionar foros" en el índice del admin. - i18n es/en (Admin): 13 claves nuevas. Verificado: build OK, /admin/forum 307->login, APIs 403 sin admin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import type { RowDataPacket, ResultSetHeader } from 'mysql2'
|
|
import { db, DB } from './db'
|
|
|
|
export interface AdminCategory {
|
|
id: number
|
|
name: string
|
|
order: number
|
|
}
|
|
|
|
export interface AdminForum {
|
|
id: number
|
|
category_id: number
|
|
name: string
|
|
description: string
|
|
order: number
|
|
visibility: number
|
|
}
|
|
|
|
/** Categorías con sus foros (incluye ocultos y sin borrar), para el admin. */
|
|
export async function listCategoriesWithForums(): Promise<
|
|
{ category: AdminCategory; forums: AdminForum[] }[]
|
|
> {
|
|
const [cats] = await db(DB.web).query<RowDataPacket[]>(
|
|
'SELECT id, name, `order` FROM forum_categories ORDER BY `order`, id',
|
|
)
|
|
const [forums] = await db(DB.web).query<RowDataPacket[]>(
|
|
'SELECT id, category_id, name, description, `order`, visibility FROM forums WHERE deleted = 0 ORDER BY `order`, id',
|
|
)
|
|
return cats.map((c) => ({
|
|
category: { id: c.id, name: c.name, order: c.order },
|
|
forums: forums
|
|
.filter((f) => f.category_id === c.id)
|
|
.map((f) => ({
|
|
id: f.id,
|
|
category_id: f.category_id,
|
|
name: f.name,
|
|
description: f.description,
|
|
order: f.order,
|
|
visibility: f.visibility,
|
|
})),
|
|
}))
|
|
}
|
|
|
|
export async function createCategory(name: string, order: number): Promise<number> {
|
|
const [res] = await db(DB.web).query<ResultSetHeader>(
|
|
'INSERT INTO forum_categories (name, `order`, created_at) VALUES (?, ?, NOW())',
|
|
[name, order],
|
|
)
|
|
return res.insertId
|
|
}
|
|
|
|
/** Borra una categoría solo si no tiene foros (no borrados). */
|
|
export async function deleteCategory(id: number): Promise<boolean> {
|
|
const [f] = await db(DB.web).query<RowDataPacket[]>(
|
|
'SELECT COUNT(*) AS n FROM forums WHERE category_id = ? AND deleted = 0',
|
|
[id],
|
|
)
|
|
if (Number(f[0]?.n ?? 0) > 0) return false
|
|
await db(DB.web).query('DELETE FROM forum_categories WHERE id = ?', [id])
|
|
return true
|
|
}
|
|
|
|
export async function createForum(
|
|
categoryId: number,
|
|
name: string,
|
|
description: string,
|
|
order: number,
|
|
visibility: number,
|
|
): Promise<number> {
|
|
const [res] = await db(DB.web).query<ResultSetHeader>(
|
|
'INSERT INTO forums (category_id, name, description, `order`, type, visibility, deleted, created_at, updated_at) ' +
|
|
'VALUES (?, ?, ?, ?, 0, ?, 0, NOW(), NOW())',
|
|
[categoryId, name, description, order, visibility ? 1 : 0],
|
|
)
|
|
return res.insertId
|
|
}
|
|
|
|
/** Borrado lógico de un foro (deleted = 1). */
|
|
export async function deleteForum(id: number): Promise<void> {
|
|
await db(DB.web).query('UPDATE forums SET deleted = 1 WHERE id = ?', [id])
|
|
}
|
|
|
|
/** Cambia la visibilidad de un foro. */
|
|
export async function setForumVisibility(id: number, visible: boolean): Promise<void> {
|
|
await db(DB.web).query('UPDATE forums SET visibility = ? WHERE id = ?', [visible ? 1 : 0, id])
|
|
}
|