diff --git a/.gitignore b/.gitignore index c618026..da15b0f 100755 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ .coverage/ *.sqlite3 *.sqlite +/logs /logs/*/*.json /logs/*/*/*.json *.gz diff --git a/content b/content index b06e407..b09f15a 160000 --- a/content +++ b/content @@ -1 +1 @@ -Subproject commit b06e40705ee75e462bb82b090b01de225f80284e +Subproject commit b09f15adaeea68955ce64ec568ca86cc12261772 diff --git a/src/middleware/baseContext.js b/src/middleware/baseContext.js index f64ca1d..36533a4 100644 --- a/src/middleware/baseContext.js +++ b/src/middleware/baseContext.js @@ -1,14 +1,62 @@ // src/middleware/baseContext.js -const getBaseContext = require("../utils/baseContext"); const { qualifyLink } = require("../utils/qualifyLinks"); const { generateToken } = require("../utils/adminToken"); -module.exports = async function baseContextMiddleware(req, res, next) { +// src/utils/baseContext.js +const path = require("path"); +const getPostsMenu = require("../services/postsMenuService"); +const { formatMonth } = require("../utils/formatMonth"); +const { qualifyNavLinks } = require("../utils/qualifyLinks.js"); +const { baseUrl } = require("../utils/baseUrl.js"); +const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); +const processMenuLinks = require("../utils/processMenuLinks"); + +const getSiteTitle = (owner) => `${owner}'s Software Blog`; + +const POSTS_DIR = path.join(__dirname, "../../content/posts"); +const DEFAULT_CONTEXT = { + showSidebar: true, + showFooter: true, +}; + +module.exports.attachBaseContextGetter = async (req, res, next) => { + req.getBaseContext = async (isAuthenticated, overrides = {}) => { + const filteredNavLinks = processMenuLinks( + navLinks, + isAuthenticated, + req.path + ); + const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); + const menu = await getPostsMenu(POSTS_DIR); + const siteOwner = process.env.SITE_OWNER; + + const context = { + title: getSiteTitle(siteOwner), + siteOwner, + originCountry: process.env.COUNTRY, + hCaptchaKey: process.env.HCAPTCHA_KEY, + navLinks: qualifiedNavLinks, + years: menu, + formatMonth, + baseUrl, + isAuthenticated, + ...DEFAULT_CONTEXT, + ...overrides, + }; + + return context; + }; + next(); +}; + +module.exports.buildBaseContext = async (req, res, next) => { const isAuthenticated = req.isAuthenticated; const token = generateToken(); const adminLoginUrl = qualifyLink(`/${token}`); - const baseContext = await getBaseContext(isAuthenticated, { adminLoginUrl }); + const baseContext = await req.getBaseContext(isAuthenticated, { + adminLoginUrl, + }); res.locals.baseContext = baseContext; res.renderWithBaseContext = (template, overrides = {}) => { diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.js index 42365c6..4488c90 100644 --- a/src/middleware/errorHandler.js +++ b/src/middleware/errorHandler.js @@ -1,6 +1,5 @@ // src/middleware/errorHandler const crypto = require("crypto"); -const getBaseContext = require("../utils/baseContext"); const { getErrorContext } = require("../utils/errorContext"); const { buildErrorRenderContext } = require("../utils/buildErrorRenderContext"); const { isDev } = require("../utils/env"); @@ -74,7 +73,10 @@ metadata: err.metadata, }); - const errorPageContext = await getBaseContext(req?.isAuthenticated, context); + const errorPageContext = await req.getBaseContext( + req?.isAuthenticated, + context + ); res.status(errorContext.statusCode); res.renderGenericMessage(errorPageContext); }; diff --git a/src/middleware/index.js b/src/middleware/index.js index 0fd1234..1767be6 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -9,7 +9,7 @@ const { applyProductionSecurity } = require("./applyProductionSecurity"); const validateRequestIntegrity = require("./validateRequestIntegrity"); const errorHandler = require("./errorHandler"); -const baseContext = require("./baseContext"); +const { attachBaseContextGetter, buildBaseContext } = require("./baseContext"); const hbs = require("./hbs"); const authCheck = require("./authCheck"); const { redirectMiddleware } = require("./redirect"); @@ -106,7 +106,7 @@ app.use(authCheck); // Setup handlebars - app.use(baseContext); + app.use(attachBaseContextGetter, buildBaseContext); // Setup production environment if ( diff --git a/src/routes/contact.js b/src/routes/contact.js index feab0a0..16ac487 100644 --- a/src/routes/contact.js +++ b/src/routes/contact.js @@ -1,49 +1,8 @@ -// // src/routes/contact.js -// const express = require("express"); -// const router = express.Router(); -// const sendContactMail = require("../utils/sendContactMail"); -// const getBaseContext = require("../utils/baseContext"); -// const formLimiter = require("../utils/formLimiter"); -// const verifyHCaptcha = require("../utils/verifyHCaptcha"); - -// router.post("/contact", formLimiter, async (req, res, next) => { -// try { -// const { name, email, message, hcaptchaToken } = req.body; -// if (!hcaptchaToken) { -// return res.status(400).send("Captcha token missing"); -// } -// const valid = await verifyHCaptcha(hcaptchaToken); -// if (!valid) { -// return res.status(400).send("Captcha verification failed"); -// } -// await sendContactMail({ name, email, message }); -// res.redirect("/contact/thankyou"); -// } catch (err) { -// next(err); -// } -// }); - -// router.get("/contact", async (req, res) => { -// const context = await getBaseContext({ -// csrfToken: res.locals.csrfToken, -// title: "Contact", -// }); -// res.render("pages/contact.handlebars", context); -// }); - -// router.get("/contact/thankyou", async (req, res) => { -// const context = await getBaseContext({ -// title: "Thank You", -// }); -// res.render("pages/thankyou.handlebars", context); -// }); - -// module.exports = router; // src/routes/contact.js const express = require("express"); const router = express.Router(); const sendContactMail = require("../utils/sendContactMail"); -// const getBaseContext = require("../utils/baseContext"); + const formLimiter = require("../utils/formLimiter"); const verifyHCaptcha = require("../utils/verifyHCaptcha"); const crypto = require("crypto"); diff --git a/src/utils/baseContext.js b/src/utils/baseContext.js deleted file mode 100644 index ca43b5a..0000000 --- a/src/utils/baseContext.js +++ /dev/null @@ -1,42 +0,0 @@ -// src/utils/baseContext.js -const path = require("path"); -const getPostsMenu = require("../services/postsMenuService"); -const { formatMonth } = require("./formatMonth"); -const { qualifyNavLinks } = require("./qualifyLinks.js"); -const { baseUrl } = require("./baseUrl.js"); -const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); -const filterSecureLinks = require("../utils/filterSecureLinks"); - -const getSiteTitle = (owner) => `${owner}'s Software Blog`; - -const POSTS_DIR = path.join(__dirname, "../../content/posts"); -const DEFAULT_CONTEXT = { - showSidebar: true, - showFooter: true, -}; - -module.exports = async function getBaseContext( - isAuthenticated, - overrides = {} -) { - const filteredNavLinks = filterSecureLinks(navLinks, isAuthenticated); - const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); - const menu = await getPostsMenu(POSTS_DIR); - const siteOwner = process.env.SITE_OWNER; - - const context = { - title: getSiteTitle(siteOwner), - siteOwner, - originCountry: process.env.COUNTRY, - hCaptchaKey: process.env.HCAPTCHA_KEY, - navLinks: qualifiedNavLinks, - years: menu, - formatMonth, - baseUrl, - isAuthenticated, - ...DEFAULT_CONTEXT, - ...overrides, - }; - - return context; -}; diff --git a/src/utils/docsContext.js b/src/utils/docsContext.js index db407dc..e61c095 100644 --- a/src/utils/docsContext.js +++ b/src/utils/docsContext.js @@ -7,7 +7,7 @@ const { baseUrl } = require("./baseUrl"); const generateDocsMenuModel = require("./generateDocsMenuModel"); const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); -const filterSecureLinks = require("../utils/filterSecureLinks"); +const processMenuLinks = require("../utils/processMenuLinks"); const getSiteTitle = (owner) => `${owner}'s Software Blog`; @@ -40,7 +40,7 @@ isAuthenticated, overrides = {} ) { - const filteredNavLinks = filterSecureLinks(navLinks, isAuthenticated); + const filteredNavLinks = processMenuLinks(navLinks, isAuthenticated); const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); const siteOwner = process.env.SITE_OWNER; diff --git a/src/utils/filterSecureLinks.js b/src/utils/filterSecureLinks.js deleted file mode 100644 index cc1a50d..0000000 --- a/src/utils/filterSecureLinks.js +++ /dev/null @@ -1,14 +0,0 @@ -// src/utils/filterSecureLinks.js -function filterSecureLinks(links, isAuthenticated) { - return links - .filter(link => isAuthenticated || !link.secure) - .map(link => { - const item = { ...link }; - if (item.submenu) { - item.submenu = filterSecureLinks(item.submenu, isAuthenticated); - if (!item.submenu.length) delete item.submenu; - } - return item; - }); -} -module.exports = filterSecureLinks diff --git a/src/utils/processMenuLinks.js b/src/utils/processMenuLinks.js new file mode 100644 index 0000000..92386ad --- /dev/null +++ b/src/utils/processMenuLinks.js @@ -0,0 +1,23 @@ +// src/utils/processMenuLinks.js +function processMenuLinks(links, isAuthenticated, currentPath) { + return links + .filter((link) => isAuthenticated || !link.secure) + .map((link) => { + const item = { ...link }; + if (item.appendCurrentPath && typeof item.href === "string") { + if (currentPath !== "/" && !item.href.endsWith(currentPath)) { + item.href = item.href + currentPath; + } + } + if (item.submenu) { + item.submenu = processMenuLinks( + item.submenu, + isAuthenticated, + currentPath + ); + if (!item.submenu.length) delete item.submenu; + } + return item; + }); +} +module.exports = processMenuLinks;