Ir al contenido

Módulo User Panel

El Módulo User Panel (@zumito-team/user-panel-module) proporciona un dashboard web donde los usuarios de Discord pueden gestionar sus servidores, ver información de cuenta y acceder a páginas específicas de módulos (como colas de música). Incluye autenticación OAuth2 de Discord, soporte multi-idioma y una barra lateral dinámica que otros módulos pueden extender.

Ventana de terminal
npm install @zumito-team/user-panel-module

Agrega a tu zumito.config.ts:

bundles: ['@zumito-team/user-panel-module']

Una interfaz web completa en /panel que muestra:

  • Lista de servidores — todas las guilds donde el usuario tiene permisos de Administrador, ManageGuild o es propietario.
  • Dashboard específico por guild en /panel/:guildId con secciones de barra lateral extensibles.
  • Soporte multi-idioma con detección automática mediante cookies o cabeceras Accept-Language.

Flujo de login basado en OAuth2 de Discord mediante el módulo compartido discord-auth:

  1. El usuario visita /panel/login → redirigido a autorización de Discord (scope identify).
  2. Discord redirige de vuelta a /panel/login/callback.
  3. Se emite un JWT (panel_token, purpose: 'panel') con el perfil completo de Discord, almacenado en cookie httpOnly con 30 días de expiración.
  4. Todas las rutas del panel validan este token mediante UserPanelAuthService (que extiende DiscordAuthService).
  5. Los tokens emitidos para otros módulos (ej. Admin) son rechazados gracias al claim purpose.

| Servicio | Propósito | |---|---|---| | UserPanelNavigationService | Registrar elementos en la barra lateral del panel de usuario. | | UserPanelViewService | Renderizar páginas dentro del layout del panel (plantillas EJS). | | UserPanelAuthService | Extiende DiscordAuthService. Valida tokens JWT con purpose: 'panel' y extrae datos del usuario de Discord. | | UserPanelLanguageManager | Resolver idioma desde cookies o cabeceras, proporcionar funciones de traducción. | | UserPanelColorsService | Gestionar la paleta de colores del panel. |

MétodoRutaDescripción
GET/panelDashboard principal con lista de servidores.
GET/panel/:guildIdDashboard específico de guild.
GET/panel/loginRedirige a OAuth2 de Discord.
GET/panel/login/callbackManejador del callback OAuth2.
GET/panel/logoutLimpia la cookie de autenticación y redirige a /panel.

El módulo registra estas entradas iniciales:

  • Volver — Enlace a /panel.
  • Dashboard — Resumen de guild en /panel/:guildId con una sección general.
VariableRequeridaDescripción
DISCORD_CLIENT_IDClient ID de la aplicación Discord.
DISCORD_CLIENT_SECRETClient Secret de la aplicación Discord.
SECRET_KEYClave secreta para firma JWT.
HOSTURL del host del bot.
FRONTEND_URLURL del frontend para redirecciones.
USER_PANEL_COLORS_FILENoRuta a archivo JSON con colores personalizados.
USER_PANEL_COLORSNoString JSON con colores personalizados.

Misma estructura que el módulo admin. Configurable mediante USER_PANEL_COLORS o archivo JSON:

{
"primary": "#5865f2",
"accent": "#5865f2",
"success": "#57f287",
"warning": "#fee75c",
"danger": "#ed4245",
"foreground": "#ffffff",
"dark": {
"100": "#36393f",
"200": "#2f3136",
"300": "#292b2f",
"400": "#202225"
}
}

Otros módulos pueden registrar elementos en la barra lateral consumiendo UserPanelNavigationService:

import { ServiceContainer } from 'zumito-framework';
const nav = ServiceContainer.getService('UserPanelNavigationService');
// Registrar un elemento de navegación principal
nav.registerItem({
id: 'mi-funcionalidad',
label: 'Mi Funcionalidad',
icon: 'star',
link: '/panel/mi-funcionalidad'
});
// Registrar sub-elementos bajo una sección de un padre existente
nav.registerSubItems('dashboard', 'general', [
{
id: 'mi-sub-pagina',
label: 'Mi Sub Página',
icon: 'settings',
link: '/panel/:guildId/mi-pagina'
}
]);

Crea tus propias clases de ruta que usen UserPanelViewService.render():

import { Route, RouteMethod, ServiceContainer } from 'zumito-framework';
class MiRutaPanel extends Route {
method = RouteMethod.get;
path = '/panel/:guildId/mi-pagina';
async execute(req, res) {
const auth = ServiceContainer.getService('UserPanelAuthService');
const { isValid, data } = await auth.isLoginValid(req);
if (!isValid) return res.redirect('/panel/login');
const view = ServiceContainer.getService('UserPanelViewService');
const html = await view.render({
content: '<h1>Mi Página Personalizada</h1>',
reqPath: req.path,
req,
res
});
res.send(html);
}
}

Tu módulo debe declarar la dependencia al extender el panel:

class MiModulo extends Module {
requirements = {
services: ['UserPanelNavigationService']
};
async initialize() {
const nav = ServiceContainer.getService('UserPanelNavigationService');
nav.registerSubItems('dashboard', 'general', [
{ id: 'mi-pagina', label: 'Mi Página', link: '/panel/:guildId/mi-pagina' }
]);
}
}

El panel detecta automáticamente el idioma del usuario. Usa UserPanelLanguageManager para acceder a traducciones:

const langManager = ServiceContainer.getService('UserPanelLanguageManager');
const { t, lang } = langManager.getLanguageVariables(req, res);
const greeting = t('mensaje_bienvenida'); // Devuelve string traducido
const idiomasDisponibles = langManager.getAvailableLanguages(); // ['en', 'es', ...]

Ambos módulos comparten la misma base de autenticación mediante discord-auth, con diferentes configuraciones:

FuncionalidadAdminUser Panel
Servicio de autenticaciónAdminAuthServiceDiscordAuthServiceUserPanelAuthServiceDiscordAuthService
Servicio de vistasAdminViewServiceUserPanelViewService
Servicio de navegaciónNavigationServiceUserPanelNavigationService
Servicio de coloresAdminColorsServiceUserPanelColorsService
Nombre de cookieadmin_tokenpanel_token
Propósito JWT'admin''panel'
Clave de sub-elementosectionLabelsectionId
  • discord-auth — Servicio compartido de autenticación OAuth2 de Discord.
  • ejs — Renderizado de plantillas para páginas del panel.
  • jose — Verificación y firma de JWT.
  • zumito-framework
  • Módulo DisTube — Registra una página de cola de música en la barra lateral del panel de usuario.
  • Módulo Admin — Arquitectura similar para dashboards de nivel administrador.