Files
NightSpire/web-next/lib/admin-users.ts
T
Inna 1a45d48412 Admin: corregir columnas de account_access + enlace al panel en la cuenta
- Este build de AzerothCore usa account_access(AccountID, SecurityLevel), no el
  clásico (id, gmlevel). Se corrigen las 4 consultas (admin.ts, forum-perm.ts,
  forum.ts, admin-users.ts) -> isAdmin/forumIsModerator/gmlevel ahora funcionan.
- account page: enlace destacado al Panel de administración, visible solo si isAdmin.
- i18n es/en: Account.adminPanel.

Verificado: build OK; cuenta de prueba con SecurityLevel 3 (>= ADMIN_GMLEVEL) es admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:42:04 +00:00

89 lines
3.3 KiB
TypeScript

import type { RowDataPacket } from 'mysql2'
import { db, DB } from './db'
import { executeSoapCommand } from './soap'
export interface AccountRow {
id: number
username: string
email: string | null
last_ip: string | null
last_login: string | null
online: boolean
gmlevel: number
banned: boolean
ban_reason: string | null
ban_until: string | null // ISO, o null si permanente/no baneado
}
/** Busca cuentas por nombre de usuario o email (acore_auth.account). */
export async function searchAccounts(query: string, limit = 30): Promise<AccountRow[]> {
const like = `%${query}%`
const [rows] = await db(DB.auth).query<RowDataPacket[]>(
`SELECT a.id, a.username, a.email, a.last_ip, a.last_login, a.online,
(SELECT MAX(x.SecurityLevel) FROM account_access x WHERE x.AccountID = a.id) AS gmlevel,
b.banreason AS ban_reason, b.bandate, b.unbandate
FROM account a
LEFT JOIN account_banned b ON b.id = a.id AND b.active = 1
WHERE a.username LIKE ? OR a.email LIKE ?
ORDER BY a.username
LIMIT ?`,
[like, like, limit],
)
return rows.map((r) => {
const permanent = r.bandate != null && Number(r.unbandate) === Number(r.bandate)
return {
id: r.id,
username: r.username,
email: r.email || null,
last_ip: r.last_ip || null,
last_login: r.last_login ? new Date(r.last_login).toISOString() : null,
online: r.online === 1,
gmlevel: r.gmlevel != null ? Number(r.gmlevel) : 0,
banned: r.bandate != null,
ban_reason: r.ban_reason ?? null,
ban_until: r.bandate != null && !permanent ? new Date(Number(r.unbandate) * 1000).toISOString() : null,
}
})
}
async function getUsername(accountId: number): Promise<string | null> {
const [rows] = await db(DB.auth).query<RowDataPacket[]>('SELECT username FROM account WHERE id = ?', [accountId])
return rows[0]?.username ?? null
}
/**
* Banea una cuenta. `days` <= 0 => permanente (unbandate = bandate, convención AzerothCore).
* Escribe en account_banned (autoritativo) e intenta kickear por SOAP si está conectado.
*/
export async function banAccount(
accountId: number,
reason: string,
bannedBy: string,
days: number,
): Promise<boolean> {
const username = await getUsername(accountId)
if (!username) return false
// Desactiva un baneo activo previo para no acumular activos.
await db(DB.auth).query('UPDATE account_banned SET active = 0 WHERE id = ? AND active = 1', [accountId])
const duration = days > 0 ? Math.floor(days) * 86400 : 0
await db(DB.auth).query(
`INSERT INTO account_banned (id, bandate, unbandate, bannedby, banreason, active)
VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + ?, ?, ?, 1)`,
[accountId, duration, bannedBy, reason],
)
// Best-effort: kickear al jugador si está en línea (no bloquea el baneo si SOAP falla).
await executeSoapCommand(`.kick ${username}`).catch(() => null)
return true
}
/** Levanta el baneo activo de una cuenta. */
export async function unbanAccount(accountId: number): Promise<boolean> {
const [res] = await db(DB.auth).query<RowDataPacket[] & { affectedRows?: number }>(
'UPDATE account_banned SET active = 0 WHERE id = ? AND active = 1',
[accountId],
)
return Boolean((res as unknown as { affectedRows: number }).affectedRows)
}