From 585a351785c6d0d691206b9839813e12bdfcd275 Mon Sep 17 00:00:00 2001 From: Shni Date: Wed, 15 Oct 2025 23:32:40 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20actualizar=20la=20gesti=C3=B3n=20de=20r?= =?UTF-8?q?utas=20de=20plantillas=20y=20archivos=20est=C3=A1ticos=20para?= =?UTF-8?q?=20mejorar=20la=20flexibilidad=20y=20la=20detecci=C3=B3n=20de?= =?UTF-8?q?=20archivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/server/lib/render.ts | 180 ++++++++++++++++++++++++++++----------- src/server/lib/utils.ts | 13 ++- 3 files changed, 140 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 32bad62..6725db3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amayo", - "version": "2.0.22", + "version": "2.1.2", "description": "", "main": "src/main.ts", "scripts": { diff --git a/src/server/lib/render.ts b/src/server/lib/render.ts index b557542..71af148 100644 --- a/src/server/lib/render.ts +++ b/src/server/lib/render.ts @@ -86,8 +86,28 @@ export const renderTemplate = async ( locals: Record = {}, statusCode = 200 ) => { - const pageFile = path.join(viewsDir, "pages", `${template}.ejs`); - const layoutFile = path.join(viewsDir, "layouts", "layout.ejs"); + const candidatePaths = [ + path.join(viewsDir, "pages", `${template}.ejs`), + // fallback: lib is one level deeper, try ../views + path.join(__dirname, "..", "views", "pages", `${template}.ejs`), + // fallback: project src path + path.join( + process.cwd(), + "src", + "server", + "views", + "pages", + `${template}.ejs` + ), + ]; + const pageFile = (await findFirstExisting(candidatePaths)) || ""; + + const layoutCandidates = [ + path.join(viewsDir, "layouts", "layout.ejs"), + path.join(__dirname, "..", "views", "layouts", "layout.ejs"), + path.join(process.cwd(), "src", "server", "views", "layouts", "layout.ejs"), + ]; + const layoutFile = (await findFirstExisting(layoutCandidates)) || ""; locals.hideNavbar = typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false; locals.useDashboardNav = @@ -101,19 +121,40 @@ export const renderTemplate = async ( ? locals.selectedGuildId : null; - const pageBody = await ejs.renderFile(pageFile, locals, { async: true }); + let pageBody: string; + if (!pageFile) { + // no page template found -> return 404-friendly HTML + pageBody = `

404 - Página no encontrada

Template ${template} no disponible.

`; + statusCode = 404; + } else { + pageBody = await ejs.renderFile(pageFile, locals, { async: true }); + } const defaultTitle = `${ locals.appName ?? pkg.name ?? "Amayo Bot" } | Guía Completa`; let dashboardNavHtml: string | null = null; try { if (locals.useDashboardNav) { - const partialPath = path.join(viewsDir, "partials", "dashboard_nav.ejs"); - dashboardNavHtml = await ejs.renderFile( - partialPath, - { ...locals }, - { async: true } - ); + const partialCandidates = [ + path.join(viewsDir, "partials", "dashboard_nav.ejs"), + path.join(__dirname, "..", "views", "partials", "dashboard_nav.ejs"), + path.join( + process.cwd(), + "src", + "server", + "views", + "partials", + "dashboard_nav.ejs" + ), + ]; + const partialPath = (await findFirstExisting(partialCandidates)) || null; + if (partialPath) { + dashboardNavHtml = await ejs.renderFile( + partialPath, + { ...locals }, + { async: true } + ); + } } } catch (err) { console.warn("Failed rendering dashboard_nav partial:", err); @@ -123,54 +164,80 @@ export const renderTemplate = async ( try { const shouldShowNavbar = !locals.hideNavbar && !locals.useDashboardNav; if (shouldShowNavbar) { - const navPath = path.join(viewsDir, "partials", "navbar.ejs"); - navbarHtml = await ejs.renderFile( - navPath, - { appName: locals.appName ?? pkg.name ?? "Amayo Bot" }, - { async: true } - ); + const navCandidates = [ + path.join(viewsDir, "partials", "navbar.ejs"), + path.join(__dirname, "..", "views", "partials", "navbar.ejs"), + path.join( + process.cwd(), + "src", + "server", + "views", + "partials", + "navbar.ejs" + ), + ]; + const navPath = (await findFirstExisting(navCandidates)) || null; + if (navPath) { + navbarHtml = await ejs.renderFile( + navPath, + { appName: locals.appName ?? pkg.name ?? "Amayo Bot" }, + { async: true } + ); + } } } catch (err) { console.warn("Failed rendering navbar partial:", err); navbarHtml = null; } - const html = await ejs.renderFile( - layoutFile, - { - head: null, - scripts: null, - version: locals.version ?? pkg.version ?? "2.0.0", - djsVersion: - locals.djsVersion ?? pkg?.dependencies?.["discord.js"] ?? "15.0.0-dev", - currentDateHuman: - locals.currentDateHuman ?? - new Date().toLocaleDateString("es-ES", { - month: "long", - year: "numeric", - }), - hideNavbar: - typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false, - useDashboardNav: - typeof locals.useDashboardNav !== "undefined" - ? locals.useDashboardNav - : false, - selectedGuild: - typeof locals.selectedGuild !== "undefined" - ? locals.selectedGuild - : null, - selectedGuildId: - typeof locals.selectedGuildId !== "undefined" - ? locals.selectedGuildId - : null, - dashboardNav: dashboardNavHtml, - navbar: navbarHtml, - ...locals, - title: locals.title ?? defaultTitle, - body: pageBody, - }, - { async: true } - ); + let html: string; + if (!layoutFile) { + // If layout not available, use the page body directly + console.warn( + "Layout template not found, returning page body directly for:", + template + ); + html = pageBody; + } else { + html = await ejs.renderFile( + layoutFile, + { + head: null, + scripts: null, + version: locals.version ?? pkg.version ?? "2.0.0", + djsVersion: + locals.djsVersion ?? + pkg?.dependencies?.["discord.js"] ?? + "15.0.0-dev", + currentDateHuman: + locals.currentDateHuman ?? + new Date().toLocaleDateString("es-ES", { + month: "long", + year: "numeric", + }), + hideNavbar: + typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false, + useDashboardNav: + typeof locals.useDashboardNav !== "undefined" + ? locals.useDashboardNav + : false, + selectedGuild: + typeof locals.selectedGuild !== "undefined" + ? locals.selectedGuild + : null, + selectedGuildId: + typeof locals.selectedGuildId !== "undefined" + ? locals.selectedGuildId + : null, + dashboardNav: dashboardNavHtml, + navbar: navbarHtml, + ...locals, + title: locals.title ?? defaultTitle, + body: pageBody, + }, + { async: true } + ); + } const htmlBuffer = Buffer.from(html, "utf8"); const etag = computeEtag(htmlBuffer); @@ -214,3 +281,14 @@ export const renderTemplate = async ( res.writeHead(statusCode, applySecurityHeadersForRequest(req, headers)); res.end(respBody); }; + +async function findFirstExisting(paths: string[]): Promise { + for (const p of paths) { + try { + if (!p) continue; + const st = await fs.stat(p).catch(() => undefined); + if (st && st.isFile()) return p; + } catch {} + } + return null; +} diff --git a/src/server/lib/utils.ts b/src/server/lib/utils.ts index 841c85d..4565ea6 100644 --- a/src/server/lib/utils.ts +++ b/src/server/lib/utils.ts @@ -12,11 +12,18 @@ import { } from "node:zlib"; import path from "node:path"; import ejs from "ejs"; -import { promises as fs, readFileSync } from "node:fs"; +import { promises as fs, readFileSync, existsSync } from "node:fs"; import { prisma } from "../../core/database/prisma"; -const publicDir = path.join(__dirname, "public"); -const viewsDir = path.join(__dirname, "views"); +// Prefer project src paths (in case process.cwd differs between environments) +const projectPublic = path.join(process.cwd(), "src", "server", "public"); +const projectViews = path.join(process.cwd(), "src", "server", "views"); +const publicDir = existsSync(projectPublic) + ? projectPublic + : path.join(__dirname, "..", "public"); +const viewsDir = existsSync(projectViews) + ? projectViews + : path.join(__dirname, "..", "views"); export const MIME_TYPES: Record = { ".html": "text/html; charset=utf-8",