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 { const like = `%${query}%` const [rows] = await db(DB.auth).query( `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 { const [rows] = await db(DB.auth).query('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 { 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 { const [res] = await db(DB.auth).query( 'UPDATE account_banned SET active = 0 WHERE id = ? AND active = 1', [accountId], ) return Boolean((res as unknown as { affectedRows: number }).affectedRows) }