1a45d48412
- 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>
89 lines
3.3 KiB
TypeScript
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)
|
|
}
|