feat: actualizar la gestión de rutas de plantillas y archivos estáticos para mejorar la flexibilidad y la detección de archivos

This commit is contained in:
Shni
2025-10-15 23:32:40 -05:00
parent 9aa2c79be1
commit 585a351785
3 changed files with 140 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "amayo", "name": "amayo",
"version": "2.0.22", "version": "2.1.2",
"description": "", "description": "",
"main": "src/main.ts", "main": "src/main.ts",
"scripts": { "scripts": {

View File

@@ -86,8 +86,28 @@ export const renderTemplate = async (
locals: Record<string, any> = {}, locals: Record<string, any> = {},
statusCode = 200 statusCode = 200
) => { ) => {
const pageFile = path.join(viewsDir, "pages", `${template}.ejs`); const candidatePaths = [
const layoutFile = path.join(viewsDir, "layouts", "layout.ejs"); 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 = locals.hideNavbar =
typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false; typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false;
locals.useDashboardNav = locals.useDashboardNav =
@@ -101,19 +121,40 @@ export const renderTemplate = async (
? locals.selectedGuildId ? locals.selectedGuildId
: null; : null;
const pageBody = await ejs.renderFile(pageFile, locals, { async: true }); let pageBody: string;
if (!pageFile) {
// no page template found -> return 404-friendly HTML
pageBody = `<h1>404 - Página no encontrada</h1><p>Template ${template} no disponible.</p>`;
statusCode = 404;
} else {
pageBody = await ejs.renderFile(pageFile, locals, { async: true });
}
const defaultTitle = `${ const defaultTitle = `${
locals.appName ?? pkg.name ?? "Amayo Bot" locals.appName ?? pkg.name ?? "Amayo Bot"
} | Guía Completa`; } | Guía Completa`;
let dashboardNavHtml: string | null = null; let dashboardNavHtml: string | null = null;
try { try {
if (locals.useDashboardNav) { if (locals.useDashboardNav) {
const partialPath = path.join(viewsDir, "partials", "dashboard_nav.ejs"); const partialCandidates = [
dashboardNavHtml = await ejs.renderFile( path.join(viewsDir, "partials", "dashboard_nav.ejs"),
partialPath, path.join(__dirname, "..", "views", "partials", "dashboard_nav.ejs"),
{ ...locals }, path.join(
{ async: true } 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) { } catch (err) {
console.warn("Failed rendering dashboard_nav partial:", err); console.warn("Failed rendering dashboard_nav partial:", err);
@@ -123,54 +164,80 @@ export const renderTemplate = async (
try { try {
const shouldShowNavbar = !locals.hideNavbar && !locals.useDashboardNav; const shouldShowNavbar = !locals.hideNavbar && !locals.useDashboardNav;
if (shouldShowNavbar) { if (shouldShowNavbar) {
const navPath = path.join(viewsDir, "partials", "navbar.ejs"); const navCandidates = [
navbarHtml = await ejs.renderFile( path.join(viewsDir, "partials", "navbar.ejs"),
navPath, path.join(__dirname, "..", "views", "partials", "navbar.ejs"),
{ appName: locals.appName ?? pkg.name ?? "Amayo Bot" }, path.join(
{ async: true } 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) { } catch (err) {
console.warn("Failed rendering navbar partial:", err); console.warn("Failed rendering navbar partial:", err);
navbarHtml = null; navbarHtml = null;
} }
const html = await ejs.renderFile( let html: string;
layoutFile, if (!layoutFile) {
{ // If layout not available, use the page body directly
head: null, console.warn(
scripts: null, "Layout template not found, returning page body directly for:",
version: locals.version ?? pkg.version ?? "2.0.0", template
djsVersion: );
locals.djsVersion ?? pkg?.dependencies?.["discord.js"] ?? "15.0.0-dev", html = pageBody;
currentDateHuman: } else {
locals.currentDateHuman ?? html = await ejs.renderFile(
new Date().toLocaleDateString("es-ES", { layoutFile,
month: "long", {
year: "numeric", head: null,
}), scripts: null,
hideNavbar: version: locals.version ?? pkg.version ?? "2.0.0",
typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false, djsVersion:
useDashboardNav: locals.djsVersion ??
typeof locals.useDashboardNav !== "undefined" pkg?.dependencies?.["discord.js"] ??
? locals.useDashboardNav "15.0.0-dev",
: false, currentDateHuman:
selectedGuild: locals.currentDateHuman ??
typeof locals.selectedGuild !== "undefined" new Date().toLocaleDateString("es-ES", {
? locals.selectedGuild month: "long",
: null, year: "numeric",
selectedGuildId: }),
typeof locals.selectedGuildId !== "undefined" hideNavbar:
? locals.selectedGuildId typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false,
: null, useDashboardNav:
dashboardNav: dashboardNavHtml, typeof locals.useDashboardNav !== "undefined"
navbar: navbarHtml, ? locals.useDashboardNav
...locals, : false,
title: locals.title ?? defaultTitle, selectedGuild:
body: pageBody, typeof locals.selectedGuild !== "undefined"
}, ? locals.selectedGuild
{ async: true } : 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 htmlBuffer = Buffer.from(html, "utf8");
const etag = computeEtag(htmlBuffer); const etag = computeEtag(htmlBuffer);
@@ -214,3 +281,14 @@ export const renderTemplate = async (
res.writeHead(statusCode, applySecurityHeadersForRequest(req, headers)); res.writeHead(statusCode, applySecurityHeadersForRequest(req, headers));
res.end(respBody); res.end(respBody);
}; };
async function findFirstExisting(paths: string[]): Promise<string | null> {
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;
}

View File

@@ -12,11 +12,18 @@ import {
} from "node:zlib"; } from "node:zlib";
import path from "node:path"; import path from "node:path";
import ejs from "ejs"; 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"; import { prisma } from "../../core/database/prisma";
const publicDir = path.join(__dirname, "public"); // Prefer project src paths (in case process.cwd differs between environments)
const viewsDir = path.join(__dirname, "views"); 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<string, string> = { export const MIME_TYPES: Record<string, string> = {
".html": "text/html; charset=utf-8", ".html": "text/html; charset=utf-8",