Full-stack Next.js: la home lee MySQL directamente (sin Django)
Pivote a Next.js full-stack (Django saldrá del todo en el cutover). Next accede a las mismas BD MySQL que Django: - lib/db.ts: pools mysql2 (django_wow + acore_*), singleton en globalThis, creds en web-next/.env.local (gitignored, copiadas del .env de Django). - lib/home.ts: getNoticias (home_noticia), getServerStatus (home_serverselection + online desde acore_characters + TCP check + expansión por gamebuild, incl. Classic). - app/page.tsx: home SSR que llama a lib/home (ya NO a la API Django). Se elimina lib/api.ts. Verificado: con el servicio Django PARADO, la home de Next sigue sirviendo 200 con datos reales -> lee MySQL directo, Django fuera del circuito. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import net from 'node:net'
|
||||
import type { RowDataPacket } from 'mysql2'
|
||||
import { db, DB } from './db'
|
||||
|
||||
export interface Noticia {
|
||||
id: number
|
||||
titulo: string
|
||||
fecha: string | null
|
||||
contenido: string
|
||||
enlace: string | null
|
||||
}
|
||||
|
||||
export interface ServerStatus {
|
||||
server_name: string | null
|
||||
address: string | null
|
||||
expansion: string
|
||||
online_characters: number
|
||||
status: 'Online' | 'Offline'
|
||||
}
|
||||
|
||||
/** Etiqueta de expansión por build del cliente (incluye WoW Classic; Nova WoW = 3.4.3 = 54261). */
|
||||
export function expansionFromGamebuild(gamebuild: number): string {
|
||||
if (gamebuild === 12340) return 'WotLK'
|
||||
if (gamebuild >= 44000 && gamebuild <= 54261) return 'WotLK Classic'
|
||||
if (gamebuild === 15595) return 'Cataclysm'
|
||||
if (gamebuild >= 54262 && gamebuild <= 60000) return 'Cataclysm Classic'
|
||||
return 'Unknown'
|
||||
}
|
||||
|
||||
export async function getNoticias(limit = 50): Promise<Noticia[]> {
|
||||
const [rows] = await db(DB.default).query<RowDataPacket[]>(
|
||||
'SELECT id, titulo, contenido, fecha_publicacion, enlace FROM home_noticia ORDER BY fecha_publicacion DESC LIMIT ?',
|
||||
[limit],
|
||||
)
|
||||
return rows.map((r) => ({
|
||||
id: r.id,
|
||||
titulo: r.titulo,
|
||||
fecha: r.fecha_publicacion ? new Date(r.fecha_publicacion).toISOString() : null,
|
||||
contenido: r.contenido,
|
||||
enlace: r.enlace || null,
|
||||
}))
|
||||
}
|
||||
|
||||
async function getOnlineCount(): Promise<number> {
|
||||
try {
|
||||
const [rows] = await db(DB.characters).query<RowDataPacket[]>(
|
||||
'SELECT COUNT(*) AS n FROM characters WHERE online = 1',
|
||||
)
|
||||
return Number(rows[0]?.n ?? 0)
|
||||
} catch {
|
||||
// La BD de personajes (AzerothCore) puede no estar poblada todavía.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function checkServerStatus(host: string, port: number, timeout = 1000): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (!host || host === 'N/A') return resolve(false)
|
||||
const socket = new net.Socket()
|
||||
const done = (ok: boolean) => {
|
||||
socket.destroy()
|
||||
resolve(ok)
|
||||
}
|
||||
socket.setTimeout(timeout)
|
||||
socket.once('connect', () => done(true))
|
||||
socket.once('timeout', () => done(false))
|
||||
socket.once('error', () => done(false))
|
||||
socket.connect(port, host)
|
||||
})
|
||||
}
|
||||
|
||||
export async function getServerStatus(): Promise<ServerStatus | null> {
|
||||
const [rows] = await db(DB.default).query<RowDataPacket[]>(
|
||||
'SELECT name, address, port, gamebuild FROM home_serverselection LIMIT 1',
|
||||
)
|
||||
const s = rows[0]
|
||||
if (!s) return null
|
||||
const [online, up] = await Promise.all([getOnlineCount(), checkServerStatus(s.address, s.port)])
|
||||
return {
|
||||
server_name: s.name,
|
||||
address: s.address,
|
||||
expansion: expansionFromGamebuild(s.gamebuild),
|
||||
online_characters: online,
|
||||
status: up ? 'Online' : 'Offline',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user