diff --git a/package.json b/package.json index 5592f03..77f5d40 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "testing": "pm2 start ecosystem.config.js --only expressjs-blog-testing", "stop-main": "node_modules/pm2/bin/pm2 delete expressjs-blog-main", "stop-testing": "node_modules/pm2/bin/pm2 delete expressjs-blog-testing", - "test:prepush": "node scripts/test-prepush.js", - "test:postreceive": "node scripts/test-postreceive.js", + "test:prepush": "node scripts/test-prepush.js --no-color", + "test:postreceive": "node scripts/test-postreceive.js --no-color", "update-submodules": "git submodule update --init --recursive --remote" }, "imports": { @@ -55,6 +55,7 @@ "rss": "^1.2.2", "serve-favicon": "^2.5.1", "sharp": "^0.34.3", + "smol-toml": "^1.6.0", "sqlite3": "^5.1.7", "to-ico": "^1.1.5", "validator": "^13.15.15", @@ -65,7 +66,7 @@ }, "devDependencies": { "@faker-js/faker": "^9.9.0", - "chai": "^5.2.1", + "chai": "^6.2.2", "chai-as-promised": "^8.0.1", "fast-check": "^4.2.0", "mocha": "^11.7.1", diff --git a/src/app.js b/src/app.js index f26debd..b64cdd7 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,8 @@ // src/app.js require("dotenv").config(); +const { network: c } = require("./config/loader"); + const net = require("net"); const setupMiddleware = require("./middleware"); const { @@ -12,22 +14,15 @@ const { startTokenCleanup } = require("./utils/tokenCleanup"); const { cleanupOldSessions } = require("./utils/logManager"); -const SERVER_PORT = process.env.SERVER_PORT || 3400; -const SERVER_LISTEN_LOG = (port) => - `Server listening on http://localhost:${port}`; -const NODE_ENV_LOG = `NODE_ENV: ${process.env.NODE_ENV}`; - cleanupOldSessions(); startTokenCleanup(); -// const winstonLogger = createWinstonLogger(); - const app = setupMiddleware(); const server = net.createServer(); server.once("error", (err) => { if (err.code === "EADDRINUSE") { - winstonLogger.error(`Port ${SERVER_PORT} is already in use.`); + winstonLogger.error(`Port ${c.port} is already in use.`); process.exit(1); } else { throw err; @@ -37,13 +32,15 @@ server.once("listening", () => { server.close(); - app.listen(SERVER_PORT, () => { - winstonLogger.info(SERVER_LISTEN_LOG(SERVER_PORT)); - winstonLogger.info(NODE_ENV_LOG); + app.listen(c.port, () => { + winstonLogger.info( + `Server listening on ${c.schema}://${c.domain}:${c.port}`, + ); + winstonLogger.info(`NODE_ENV: ${c.node_env}`); }); }); -server.listen(SERVER_PORT); +server.listen(c.port); process.on("uncaughtException", handleUncaughtException); process.on("unhandledRejection", handleUnhandledRejection); diff --git a/src/config/emailConfig.js b/src/config/emailConfig.js index 6530620..7c7ebae 100644 --- a/src/config/emailConfig.js +++ b/src/config/emailConfig.js @@ -1,14 +1,6 @@ // src/config/emailConfig.js const path = require("path"); -const MAIL_DOMAIN = process.env.MAIL_DOMAIN; -const MAIL_USER = process.env.MAIL_USER; -const DEFAULT_SUBJECT = "New Contact Form Submission"; -const EMAIL_LOG_PATH = path.join(__dirname, "../../data/emails.json"); - module.exports = { - MAIL_DOMAIN, - MAIL_USER, - DEFAULT_SUBJECT, - EMAIL_LOG_PATH, + EMAIL_LOG_PATH: path.join(__dirname, "../../data/emails.json"), }; diff --git a/src/config/loader.js b/src/config/loader.js new file mode 100644 index 0000000..377301a --- /dev/null +++ b/src/config/loader.js @@ -0,0 +1,78 @@ +const fs = require("fs"); +const path = require("path"); +const { parse } = require("smol-toml"); +const toml = require("smol-toml"); + +function hydrate(c = {}) { + const schema = c?.network?.schema || process.env.SERVER_SCHEMA || "http"; + const domain = c?.network?.domain || process.env.SERVER_DOMAIN || "localhost"; + const address = c?.network?.address || process.env.ADDRESS || "0.0.0.0"; + const port = c?.network?.port || process.env.SERVER_PORT || 3400; + + return { + meta: { + log_level: c?.meta?.log_level || process.env.LOG_LEVEL || "info", + node_env: c?.meta?.node_env || process.env.NODE_ENV || "development", + site_owner: c?.meta?.site_owner || process.env.SITE_OWNER || undefined, + country: c?.meta?.country || process.env.COUNTRY || undefined, + }, + public: { + schema: c?.public?.schema || process.env.SERVER_SCHEMA || schema, + port: c?.public?.port || process.env.SERVER_PORT || port, + domain: c?.public?.domain || process.env.SERVER_DOMAIN || domain, + address: c?.public?.address || process.env.SERVER_ADDRESS || address, + }, + network: { + domain, + address, + schema, + port, + }, + auth: { + verify: c?.auth?.verify || process.env.AUTH_VERIFY || null, + login: c?.auth?.login || process.env.AUTH_LOGIN || null, + cache_ttl: + c?.auth?.cache_ttl || + parseInt(process.env.AUTH_CACHE_TTL, 10) || + 120000, + timeout_ms: c?.auth?.timeout_ms || process.env.AUTH_TIMEOUT_MS || 5000, + }, + mail: { + secure: c?.mail?.secure || process.env.MAIL_SECURE || false, + auth: c?.mail?.auth || process.env.MAIL_AUTH || null, + domain: c?.mail?.domain || process.env.MAIL_DOMAIN || "localhost", + port: c?.mail?.port || process.env.MAIL_PORT || 1025, + newsletter: + c?.mail?.newsletter || + process.env.MAIL_NEWSLETTER || + "newsletter@localhost", + pass: c?.mail?.pass || null, + }, + hcaptcha: { + secret: c?.hcaptcha?.secret || process.env.HCAPTCHA_SECRET || null, + key: c?.hcaptcha?.key || process.env.HCAPTCHA_KEY || null, + }, + }; +} +function loadConfig() { + // Use a simple flag parser (e.g., --config) + const configIdx = process.argv.indexOf("--config"); + const configPath = configIdx !== -1 ? process.argv[configIdx + 1] : null; + + if (!configPath) { + console.info("Notice: No config file provided. Use --config "); + console.info(" Using defaults"); + return hydrate(); + } + + try { + const raw = fs.readFileSync(path.resolve(configPath), "utf8"); + const toml_config = toml.parse(raw); + return hydrate(toml_config); + } catch (err) { + console.error(`Failed to load config at ${configPath}:`, err.message); + process.exit(1); + } +} + +module.exports = loadConfig(); diff --git a/src/constants/authConstants.js b/src/constants/authConstants.js index a3a0a18..891b2cf 100644 --- a/src/constants/authConstants.js +++ b/src/constants/authConstants.js @@ -1,7 +1,6 @@ // constants/authConstants.js -const VERIFY_URL = process.env.AUTH_VERIFY; -const CACHE_TTL = parseInt(process.env.AUTH_CACHE_TTL, 10) || 120000; // 2 minutes default -const AUTH_TIMEOUT_MS = 5000; // 5 second timeout +const { auth } = require("../config/loader"); +const { verify: verify_url, cache_ttl, timeout_ms } = auth; const LOG_MESSAGES = { AUTH_SERVER_UNAVAILABLE: @@ -9,8 +8,8 @@ }; module.exports = { - VERIFY_URL, - CACHE_TTL, - AUTH_TIMEOUT_MS, + VERIFY_URL: verify_url, + CACHE_TTL: cache_ttl, + AUTH_TIMEOUT_MS: timeout_ms, LOG_MESSAGES, }; diff --git a/src/controllers/adminTokenController.js b/src/controllers/adminTokenController.js index 86acc37..c47a72d 100644 --- a/src/controllers/adminTokenController.js +++ b/src/controllers/adminTokenController.js @@ -1,5 +1,6 @@ const { validateToken, cleanupTokens } = require("../utils/adminToken"); const SecurityEvent = require("../utils/SecurityEvent"); +const { auth } = require("../config/loader"); exports.cleanupTokensMiddleware = (req, res, next) => { if (Math.random() < 0.1) { @@ -27,7 +28,7 @@ ? referrer : `${scheme}://${host}${referrer}`; - const adminLoginUrl = `${process.env.AUTH_LOGIN}${redirectTo}`; + const adminLoginUrl = `${auth.login}${redirectTo}`; res.set("Content-Type", "text/html"); res.customRedirect(adminLoginUrl, 301); console.log("test"); diff --git a/src/controllers/blogControllers.js b/src/controllers/blogControllers.js index 9fa66b2..86558bd 100644 --- a/src/controllers/blogControllers.js +++ b/src/controllers/blogControllers.js @@ -4,12 +4,15 @@ const path = require("path"); const fsSync = require("fs"); const crypto = require("crypto"); +const { meta } = require("../config/loader"); const matter = require("gray-matter"); const { getAllPosts } = require("../utils/postFileUtils"); const HttpError = require("../utils/HttpError"); +const { node_env } = meta; + exports.blogPost = async (req, res, next) => { const { year, month, name } = req.params; @@ -33,7 +36,7 @@ "../../content/posts", year, month, - `${name}.md` + `${name}.md`, ); try { @@ -62,8 +65,7 @@ const { data: frontmatter, content } = matter(fileContent); if ( !frontmatter.published && - (process.env.NODE_ENV === "production" || - process.env.NODE_ENV === "testing") + (node_env === "production" || node_env === "testing") ) { throw new Error("Attempted to access an unpublished page in production"); } @@ -88,9 +90,7 @@ const publishedPosts = allPosts.filter( (post) => - post.published || - process.env.NODE_ENV === "production" || - process.env.NODE_ENV === "testing" + post.published || node_env === "production" || node_env === "testing", ); // Sort posts descending by date publishedPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); @@ -101,7 +101,7 @@ const lastModified = publishedPosts.length > 0 ? new Date( - Math.max(...publishedPosts.map((p) => new Date(p.date).getTime())) + Math.max(...publishedPosts.map((p) => new Date(p.date).getTime())), ).toUTCString() : new Date().toUTCString(); diff --git a/src/controllers/rssFeedController.js b/src/controllers/rssFeedController.js index 772b238..2f048d0 100644 --- a/src/controllers/rssFeedController.js +++ b/src/controllers/rssFeedController.js @@ -1,9 +1,12 @@ // routes/rss.js const generateRSSFeed = require("../services/rssFeedService"); +const { public } = require("../config/loader"); module.exports = async (req, res) => { - const domain = process.env.DOMAIN; - const xml = await generateRSSFeed("content/posts", `https://${domain}`); + const xml = await generateRSSFeed( + "content/posts", + `https://${public.domain}`, + ); res.set("Content-Type", "application/rss+xml"); res.send(xml); }; diff --git a/src/controllers/sitemapControllers.js b/src/controllers/sitemapControllers.js index 0e5c42d..5a36562 100644 --- a/src/controllers/sitemapControllers.js +++ b/src/controllers/sitemapControllers.js @@ -7,11 +7,12 @@ // Precompile XML template once const xmlTplSrc = fs.readFileSync( path.resolve(__dirname, "../views/pages/sitemap-xml.handlebars"), - "utf-8" + "utf-8", ); const xmlTpl = Handlebars.compile(xmlTplSrc); async function getSitemapHtml(req, res, next) { + re; try { const sitemap = await sitemapService.getCompleteSitemap(); const context = { diff --git a/src/middleware/applyProductionSecurity.js b/src/middleware/applyProductionSecurity.js index 2ff2ffb..4d66302 100644 --- a/src/middleware/applyProductionSecurity.js +++ b/src/middleware/applyProductionSecurity.js @@ -13,6 +13,8 @@ CSP_DIRECTIVES, } = require("../config/securityConfig"); +const { meta } = require("../config/loader"); + const disablePoweredBy = (req, res, next) => { req.app.disable("x-powered-by"); next(); @@ -23,7 +25,7 @@ return next(); } if ( - process.env.NODE_ENV === "production" && + meta.node_env === "production" && LOCALHOST_HOSTNAMES.includes(req.hostname) ) { req.log.info(`Method: ${req.method} Path ${req.path}`); diff --git a/src/middleware/authCheck.js b/src/middleware/authCheck.js index 63ab9a2..07b3987 100644 --- a/src/middleware/authCheck.js +++ b/src/middleware/authCheck.js @@ -1,11 +1,8 @@ // middleware/authCheck.js const fetch = require("node-fetch"); -const { - VERIFY_URL, - CACHE_TTL, - AUTH_TIMEOUT_MS, - LOG_MESSAGES, -} = require("../constants/authConstants"); +const { auth } = require("../config/loader"); +const { verify: verify_url, cache_ttl, timeout_ms } = auth; +const { LOG_MESSAGES } = require("../constants/authConstants"); // Simple in-memory cache const authCache = new Map(); @@ -15,17 +12,17 @@ } function isCacheValid(entry) { - return entry && Date.now() - entry.timestamp < CACHE_TTL; + return entry && Date.now() - entry.timestamp < cache_ttl; } setInterval(() => { const now = Date.now(); for (const [key, entry] of authCache.entries()) { - if (now - entry.timestamp >= CACHE_TTL) { + if (now - entry.timestamp >= cache_ttl) { authCache.delete(key); } } -}, CACHE_TTL); +}, cache_ttl); const SAFE_IPS = ["192.168.1.200", "192.168.1.50"]; module.exports = async (req, res, next) => { @@ -60,9 +57,9 @@ try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), AUTH_TIMEOUT_MS); + const timeout = setTimeout(() => controller.abort(), auth.timeout_ms); - const resVerify = await fetch(VERIFY_URL, { + const resVerify = await fetch(verify_url, { headers: { cookie, authorization: authHeader }, credentials: "include", signal: controller.signal, diff --git a/src/middleware/baseContext.js b/src/middleware/baseContext.js index 983405d..6840173 100644 --- a/src/middleware/baseContext.js +++ b/src/middleware/baseContext.js @@ -10,6 +10,7 @@ const { baseUrl } = require("../utils/baseUrl.js"); const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); const processMenuLinks = require("../utils/processMenuLinks"); +const { meta } = require("../config/loader"); const getSiteTitle = (owner) => `${owner}'s Software Blog`; @@ -28,13 +29,13 @@ ); const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); const menu = await getPostsMenu(POSTS_DIR); - const siteOwner = process.env.SITE_OWNER; + const siteOwner = meta.site_owner; const context = { title: getSiteTitle(siteOwner), siteOwner, - originCountry: process.env.COUNTRY, - hCaptchaKey: process.env.HCAPTCHA_KEY, + originCountry: meta.country, + hCaptchaKey: meta.hcaptcha_key, navLinks: qualifiedNavLinks, years: menu, formatMonth, diff --git a/src/middleware/index.js b/src/middleware/index.js index 030d2b6..e0684d9 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -23,8 +23,9 @@ const httpLogger = require("../utils/structuredLogger"); const cacheUtils = require("./cacheUtils"); const trace = require("./trace"); +const { meta } = require("../config/loader"); -function setupApp() { +function setupApp(config) { const app = express(); app.disable("x-powered-by"); @@ -42,10 +43,7 @@ app.use(attachBaseContextGetter, buildBaseContext); // Setup production environment - if ( - process.env.NODE_ENV === "production" || - process.env.NODE_ENV === "testing" - ) { + if (meta.node_env === "production" || meta.node_env === "testing") { app.use(applyProductionSecurity); } diff --git a/src/routes/index.js b/src/routes/index.js index 42e0510..1703b84 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -19,6 +19,7 @@ const HttpError = require("../utils/HttpError"); const stack = require("../controllers/techkStackController"); +const { meta } = require("../config/loader"); const favicon = require("serve-favicon"); const faviconsPath = path.join(__dirname, "..", "..", "public", "favicons"); @@ -42,7 +43,7 @@ extensions: false, fallthrough: false, setHeaders: (res) => { - if (process.env.NODE_ENV == "production") { + if (meta.node_env == "production") { // Doesn't expire res.set("Cache-Control", "public, max-age=31536000, immutable"); } else { @@ -50,7 +51,7 @@ res.set("Cache-Control", "public, max-age=30, must-revalidate"); } }, - }) + }), ); router.use( @@ -61,7 +62,7 @@ extensions: false, fallthrough: false, setHeaders: (res) => { - if (process.env.NODE_ENV == "production") { + if (meta.node_env == "production") { // Cache for 1 day, allow revalidation res.set("Cache-Control", "public, max-age=86400, must-revalidate"); } else { @@ -69,7 +70,7 @@ res.set("Cache-Control", "public, max-age=30, must-revalidate"); } }, - }) + }), ); router.use("/favicons", express.static(faviconsPath)); diff --git a/src/routes/pages.js b/src/routes/pages.js index b28b5bd..3b75f99 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -5,15 +5,14 @@ const MarkdownRoutes = require("../utils/MarkdownRoutes"); const HtmlRoutes = require("../utils/htmlRoutes"); const csrfToken = require("../middleware/csrfToken"); +const { meta } = require("../config/loader"); const construction = new ConstructionRoutes(); const html = new HtmlRoutes(); const markdown = new MarkdownRoutes(); -if ( - process.env.NODE_ENV === "production" || - process.env.NODE_ENV === "testing" -) { +const { node_env } = meta; +if (node_env === "production" || node_env === "testing") { // construction.register("/newsletter", "Newsletter"); construction.register("/projects", "Projects"); } else { diff --git a/src/services/tagsService.js b/src/services/tagsService.js index 08543ec..0e52051 100644 --- a/src/services/tagsService.js +++ b/src/services/tagsService.js @@ -1,7 +1,7 @@ const fs = require("fs").promises; const path = require("path"); const matter = require("gray-matter"); -const glob = require("glob"); +const { glob } = require("glob"); const createExcerpt = require("../utils/createExcerpt"); const CONTENT_ROOT = path.resolve(__dirname, "../../content"); @@ -15,27 +15,31 @@ async function getPostsByTag(tag) { const allUrls = await sitemapService.getAllUrls(); - const files = await glob.promises.glob(pattern); + const files = await glob(pattern); const tagRegex = buildTagRegex(tag); const matchedPosts = []; for (const filePath of files) { const raw = await fs.readFile(filePath, "utf-8"); - const { data: frontmatter, content } = matter(raw); - const fileHash = hash(frontmatter); + try { + const { data: frontmatter, content } = matter(raw); + const fileHash = hash(frontmatter); - if (frontmatter.published !== true) continue; - if (!Array.isArray(frontmatter.tags)) continue; - if (!frontmatter.tags.some((t) => tagRegex.test(t))) continue; + if (frontmatter.published !== true) continue; + if (!Array.isArray(frontmatter.tags)) continue; + if (!frontmatter.tags.some((t) => tagRegex.test(t))) continue; - const urlMatches = allUrls.find((url) => url.id == fileHash); - matchedPosts.push({ - title: frontmatter.title || "Untitled", - loc: urlMatches.loc, - date: frontmatter.date || null, - excerpt: createExcerpt(content, 200), - }); + const urlMatches = allUrls.find((url) => url.id == fileHash); + matchedPosts.push({ + title: frontmatter.title || "Untitled", + loc: urlMatches.loc, + date: frontmatter.date || null, + excerpt: createExcerpt(content, 200), + }); + } catch (e) { + console.log("File path", filePath, e.stack); + } } return matchedPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); diff --git a/src/utils/baseUrl.js b/src/utils/baseUrl.js index 015ee03..2acba6d 100644 --- a/src/utils/baseUrl.js +++ b/src/utils/baseUrl.js @@ -1,12 +1,13 @@ // src/utils/baseUrl.js +const { public } = require("../config/loader"); function getBaseUrl({ schema = null, host = null, port = null } = {}) { - const envSchema = process.env.TEST_SCHEMA || process.env.SERVER_SCHEMA; - const envDomain = process.env.TEST_DOMAIN || process.env.SERVER_DOMAIN; - const envPort = process.env.TEST_PORT || process.env.SERVER_PORT; + const envSchema = public.schema; + const envDomain = public.domain; + const envPort = public.port; - const finalPort = envPort || port || 3000; - const finalProtocol = envSchema || schema || "https"; - const finalDomain = (envDomain || host || "localhost") + const finalPort = envPort || port; + const finalProtocol = envSchema || schema; + const finalDomain = (envDomain || host) .replace(/^https?:\/\//, "") .replace(/\/$/, ""); diff --git a/src/utils/docsContext.js b/src/utils/docsContext.js index a7b7656..1069540 100644 --- a/src/utils/docsContext.js +++ b/src/utils/docsContext.js @@ -8,6 +8,7 @@ const generateDocsMenuModel = require("./generateDocsMenuModel"); const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); const processMenuLinks = require("../utils/processMenuLinks"); +const { meta } = require("../config/loader"); const getSiteTitle = (owner) => `${owner}'s Software Blog`; @@ -38,11 +39,11 @@ */ module.exports = async function getDocsContext( isAuthenticated, - overrides = {} + overrides = {}, ) { const filteredNavLinks = processMenuLinks(navLinks, isAuthenticated); const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); - const siteOwner = process.env.SITE_OWNER; + const siteOwner = meta.site_owner; const allYamlDocs = await loadAllYamlDocs(); const currentPath = overrides.docPath || null; @@ -50,13 +51,13 @@ const docsMenu = generateDocsMenuModel( allYamlDocs, currentPath, - currentModule + currentModule, ); const context = { title: getSiteTitle(siteOwner), siteOwner, - originCountry: process.env.COUNTRY, + originCountry: meta.country, navLinks: qualifiedNavLinks, baseUrl, paths: docsMenu, diff --git a/src/utils/env.js b/src/utils/env.js index 7af3c4a..e2280b2 100644 --- a/src/utils/env.js +++ b/src/utils/env.js @@ -1,4 +1,5 @@ -const NODE_ENV = process.env.NODE_ENV || "development"; +const { meta } = require("../config/loader"); +const NODE_ENV = meta.node_env || "development"; const isProd = NODE_ENV === "production"; const isDev = NODE_ENV === "development"; diff --git a/src/utils/logManager.js b/src/utils/logManager.js index b03a0ae..4f31c65 100644 --- a/src/utils/logManager.js +++ b/src/utils/logManager.js @@ -3,12 +3,14 @@ const { winstonLogger } = require("./logging"); const logDir = path.join(__dirname, "../../logs"); +const { meta } = require("../config/loader"); +const { node_env } = meta; class LogManager { constructor(logDir, options = {}) { this.logDir = logDir; this.serverStart = Date.now(); - this.isDevelopment = process.env.NODE_ENV !== "production"; + this.isDevelopment = meta.node_env !== "production"; // Configurable thresholds this.config = { @@ -38,7 +40,7 @@ this.metricsFile = path.join(logDir, ".cleanup-metrics"); winstonLogger.info( - `LogManager initialized for ${this.isDevelopment ? "development" : "production"}` + `LogManager initialized for ${this.isDevelopment ? "development" : "production"}`, ); winstonLogger.info(`Config:`, this.currentConfig); } @@ -140,7 +142,7 @@ } catch (error) { winstonLogger.warn( `Error processing session ${sessionFolder}:`, - error.message + error.message, ); return null; } @@ -153,12 +155,12 @@ emergencyCleanup() { const sessions = this.getSessionsWithMetadata(); const keepCount = Math.floor( - sessions.length * this.currentConfig.emergencyCleanupRatio + sessions.length * this.currentConfig.emergencyCleanupRatio, ); const sessionsToDelete = sessions.slice(keepCount); winstonLogger.info( - `Keeping ${keepCount} newest sessions, deleting ${sessionsToDelete.length}` + `Keeping ${keepCount} newest sessions, deleting ${sessionsToDelete.length}`, ); let deletedCount = 0; @@ -223,7 +225,7 @@ ? `age (${session.ageHours.toFixed(1)}h)` : "count limit"; winstonLogger.info( - ` Deleted ${session.folder} - ${reason} - ${session.sizeMB.toFixed(2)}MB` + ` Deleted ${session.folder} - ${reason} - ${session.sizeMB.toFixed(2)}MB`, ); } } @@ -281,7 +283,7 @@ const stat = fs.statSync(itemPath); if (stat.isDirectory()) { fs.readdirSync(itemPath).forEach((item) => - calculateSize(path.join(itemPath, item)) + calculateSize(path.join(itemPath, item)), ); } else { totalSize += stat.size; @@ -314,26 +316,26 @@ const metrics = this.getMetrics(); winstonLogger.info(`\n=== Log Manager Status ===`); winstonLogger.info( - `Environment: ${this.isDevelopment ? "development" : "production"}` + `Environment: ${this.isDevelopment ? "development" : "production"}`, ); winstonLogger.info( - `Sessions: ${metrics.sessionCount} (max: ${this.currentConfig.maxSessionCount})` + `Sessions: ${metrics.sessionCount} (max: ${this.currentConfig.maxSessionCount})`, ); winstonLogger.info( - `Total size: ${metrics.totalSizeMB.toFixed(2)}MB (max: ${this.currentConfig.maxTotalSizeMB}MB)` + `Total size: ${metrics.totalSizeMB.toFixed(2)}MB (max: ${this.currentConfig.maxTotalSizeMB}MB)`, ); winstonLogger.info( - `Retention: ${this.currentConfig.sessionRetentionHours}h` + `Retention: ${this.currentConfig.sessionRetentionHours}h`, ); if (metrics.sessions.length > 0) { const oldest = metrics.sessions[metrics.sessions.length - 1]; const newest = metrics.sessions[0]; winstonLogger.info( - `Oldest session: ${oldest.ageHours.toFixed(1)}h old (${oldest.sizeMB.toFixed(2)}MB)` + `Oldest session: ${oldest.ageHours.toFixed(1)}h old (${oldest.sizeMB.toFixed(2)}MB)`, ); winstonLogger.info( - `Newest session: ${newest.ageHours.toFixed(1)}h old (${newest.sizeMB.toFixed(2)}MB)` + `Newest session: ${newest.ageHours.toFixed(1)}h old (${newest.sizeMB.toFixed(2)}MB)`, ); } diff --git a/src/utils/logging/config.js b/src/utils/logging/config.js index 69bbf85..94b0fd5 100644 --- a/src/utils/logging/config.js +++ b/src/utils/logging/config.js @@ -1,5 +1,6 @@ // src/utils/logging/config.js const path = require("path"); +const { meta } = require("../../config/loader"); const customLevels = { levels: { @@ -24,7 +25,7 @@ }, }; -const LOG_LEVEL = process.env.LOG_LEVEL?.toLowerCase() || "info"; +const LOG_LEVEL = meta.log_level?.toLowerCase() || "info"; const LOG_LEVELS = customLevels.levels; const projectRoot = path.join(__dirname, "..", "..", ".."); diff --git a/src/utils/postFileUtils.js b/src/utils/postFileUtils.js index d6cd372..bf52271 100644 --- a/src/utils/postFileUtils.js +++ b/src/utils/postFileUtils.js @@ -2,15 +2,18 @@ const matter = require("gray-matter"); const path = require("path"); const fs = require("fs").promises; +const { meta } = require("../config/loader"); const createExcerpt = require("./createExcerpt"); const hash = require("./hash"); +const { node_env } = meta; + async function getAllPosts(baseDir, options = {}) { const { includeUnpublished = false } = options; const years = (await fs.readdir(baseDir, { withFileTypes: true })).filter( - (dirent) => dirent.isDirectory() && /^\d{4}$/.test(dirent.name) + (dirent) => dirent.isDirectory() && /^\d{4}$/.test(dirent.name), ); const allPosts = []; @@ -37,8 +40,7 @@ // Filter unpublished posts in production unless explicitly included if ( !data.published && - (process.env.NODE_ENV === "production" || - process.env.NODE_ENV === "testing") && + (node_env === "production" || node_env === "testing") && !includeUnpublished ) { return null; @@ -57,7 +59,7 @@ frontmatter: data, // Include full frontmatter for flexibility excerpt, }; - }) + }), ); allPosts.push(...posts.filter(Boolean)); diff --git a/src/utils/sendContactMail.js b/src/utils/sendContactMail.js index 02e9683..a147e67 100644 --- a/src/utils/sendContactMail.js +++ b/src/utils/sendContactMail.js @@ -4,12 +4,7 @@ const fs = require("fs").promises; const { validateAndSanitizeEmail } = require("../utils/emailValidator"); const { winstonLogger } = require("../utils/logging"); -const { - MAIL_DOMAIN, - MAIL_USER, - DEFAULT_SUBJECT, - EMAIL_LOG_PATH, -} = require("../config/emailConfig"); +const { mail } = require("../config/loader"); // Fixed sanitizeInput function function sanitizeInput(input) { @@ -50,8 +45,8 @@ if (!valid) throw new HttpError(errorMessage, 400); const mailData = { - from: `"Contact Form" `, - to: MAIL_USER, + from: `"Contact Form" `, + to: mail.user, replyTo: `"${cleanName}" <${sanitizedEmail}>`, subject: cleanSubject, text: cleanMessage, diff --git a/src/utils/sendNewsletterSubscriptionMail.js b/src/utils/sendNewsletterSubscriptionMail.js index 85de480..9305667 100644 --- a/src/utils/sendNewsletterSubscriptionMail.js +++ b/src/utils/sendNewsletterSubscriptionMail.js @@ -1,15 +1,16 @@ // src/utils/sendNewsletterSubscriptionMail.js const transporter = require("./transporter"); const { winstonLogger } = require("./logging"); +const { mail } = require("../config/loader"); const MAIL_SUBJECT = "New Newsletter Subscription"; function getMailFrom() { - return `Newsletter `; + return `Newsletter `; } function getMailText() { - return `Please add this email to the newsletter list: ${process.env.MAIL_NEWSLETTER}`; + return `Please add this email to the newsletter list: ${mail.newsletter}`; } async function sendNewsletterSubscriptionMail({ email }) { diff --git a/src/utils/transporter.js b/src/utils/transporter.js index f2537bd..3b37491 100644 --- a/src/utils/transporter.js +++ b/src/utils/transporter.js @@ -1,18 +1,14 @@ // src/utils/transporter.js const nodemailer = require("nodemailer"); +const { mail } = require("../config/loader"); + require("dotenv").config(); -let auth = null; -if (process.env.MAIL_AUTH != "null") { - auth = { - user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS, - }; -} +let { auth } = mail; const transporter = nodemailer.createTransport({ - host: process.env.MAIL_HOST, - port: parseInt(process.env.MAIL_PORT, 10), - secure: process.env.MAIL_SECURE === "true", + host: mail.host, + port: parseInt(mail.port, 10), + secure: mail.secure === "true", auth, }); diff --git a/src/utils/verifyHCaptcha.js b/src/utils/verifyHCaptcha.js index a1d0d10..1f5f18a 100644 --- a/src/utils/verifyHCaptcha.js +++ b/src/utils/verifyHCaptcha.js @@ -1,7 +1,8 @@ const fetch = require("node-fetch"); +const { hcaptcha } = require("../config/loader"); async function verifyHCaptcha(token) { - const secret = process.env.HCAPTCHA_SECRET; // Your hCaptcha secret key + const secret = hcaptcha.secret; // Your hCaptcha secret key const response = await fetch("https://hcaptcha.com/siteverify", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, diff --git a/yarn.lock b/yarn.lock index 3ac06ab..3087ce7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -751,13 +751,6 @@ languageName: node linkType: hard -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 - languageName: node - linkType: hard - "ast-types@npm:^0.13.4": version: 0.13.4 resolution: "ast-types@npm:0.13.4" @@ -1244,16 +1237,10 @@ languageName: node linkType: hard -"chai@npm:^5.2.1": - version: 5.3.3 - resolution: "chai@npm:5.3.3" - dependencies: - assertion-error: "npm:^2.0.1" - check-error: "npm:^2.1.1" - deep-eql: "npm:^5.0.1" - loupe: "npm:^3.1.0" - pathval: "npm:^2.0.0" - checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53 +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 languageName: node linkType: hard @@ -1743,13 +1730,6 @@ languageName: node linkType: hard -"deep-eql@npm:^5.0.1": - version: 5.0.2 - resolution: "deep-eql@npm:5.0.2" - checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 - languageName: node - linkType: hard - "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -2116,7 +2096,7 @@ "@faker-js/faker": "npm:^9.9.0" better-sqlite3: "npm:^12.2.0" body-parser: "npm:^2.2.0" - chai: "npm:^5.2.1" + chai: "npm:^6.2.2" chai-as-promised: "npm:^8.0.1" compression: "npm:^1.8.1" cookie-parser: "npm:^1.4.7" @@ -2153,6 +2133,7 @@ serve-favicon: "npm:^2.5.1" sharp: "npm:^0.34.3" sinon: "npm:^21.0.0" + smol-toml: "npm:^1.6.0" sqlite3: "npm:^5.1.7" to-ico: "npm:^1.1.5" validator: "npm:^13.15.15" @@ -3560,13 +3541,6 @@ languageName: node linkType: hard -"loupe@npm:^3.1.0": - version: 3.2.1 - resolution: "loupe@npm:3.2.1" - checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 - languageName: node - linkType: hard - "lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -4560,13 +4534,6 @@ languageName: node linkType: hard -"pathval@npm:^2.0.0": - version: 2.0.1 - resolution: "pathval@npm:2.0.1" - checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 - languageName: node - linkType: hard - "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -5643,6 +5610,13 @@ languageName: node linkType: hard +"smol-toml@npm:^1.6.0": + version: 1.6.0 + resolution: "smol-toml@npm:1.6.0" + checksum: 10c0/baf33bb6cd914d481329e31998a12829cd126541458ba400791212c80f1245d5b27dac04a56a52c02b287d2a494f1628c05fc19643286b258b2e0bb9fe67747c + languageName: node + linkType: hard + "socks-proxy-agent@npm:^6.0.0": version: 6.2.1 resolution: "socks-proxy-agent@npm:6.2.1"