diff --git a/.gitignore b/.gitignore
index 88f0e6b..dfa72c6 100755
--- a/.gitignore
+++ b/.gitignore
@@ -69,3 +69,5 @@
/ Coverage directories
.coverage/
+*.sqlite3
+*.sqlite
diff --git a/public/css/site-map.css b/public/css/site-map.css
deleted file mode 100644
index eae67d8..0000000
--- a/public/css/site-map.css
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Sitemap base styling */
-nav.sitemap {
- max-width: 800px;
- margin: 2rem auto;
- padding: 1rem 2rem;
- font-family: sans-serif;
- background-color: #f8f9fa;
- border: 1px solid #ddd;
- border-radius: 8px;
-}
-
-nav.sitemap h1 {
- font-size: 1.8rem;
- margin-bottom: 1rem;
- text-align: center;
- color: #333;
-}
-
-/* Top-level list */
-nav.sitemap ul {
- list-style-type: none;
- padding-left: 0;
- margin: 0;
-}
-
-nav.sitemap li {
- margin: 0.5rem 0;
- position: relative;
-}
-
-/* Links */
-nav.sitemap a {
- text-decoration: none;
- color: #007acc;
- font-weight: 500;
-}
-
-nav.sitemap a:hover {
- text-decoration: underline;
-}
-
-/* Nested lists */
-nav.sitemap li > ul {
- margin-top: 0.3rem;
- margin-left: 1.5rem;
- padding-left: 1rem;
- border-left: 2px solid #ddd;
-}
-
-nav.sitemap li > ul li::before {
- content: "→ ";
- color: #999;
-}
diff --git a/public/css/sitemap.css b/public/css/sitemap.css
new file mode 100644
index 0000000..eae67d8
--- /dev/null
+++ b/public/css/sitemap.css
@@ -0,0 +1,53 @@
+/* Sitemap base styling */
+nav.sitemap {
+ max-width: 800px;
+ margin: 2rem auto;
+ padding: 1rem 2rem;
+ font-family: sans-serif;
+ background-color: #f8f9fa;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+nav.sitemap h1 {
+ font-size: 1.8rem;
+ margin-bottom: 1rem;
+ text-align: center;
+ color: #333;
+}
+
+/* Top-level list */
+nav.sitemap ul {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+}
+
+nav.sitemap li {
+ margin: 0.5rem 0;
+ position: relative;
+}
+
+/* Links */
+nav.sitemap a {
+ text-decoration: none;
+ color: #007acc;
+ font-weight: 500;
+}
+
+nav.sitemap a:hover {
+ text-decoration: underline;
+}
+
+/* Nested lists */
+nav.sitemap li > ul {
+ margin-top: 0.3rem;
+ margin-left: 1.5rem;
+ padding-left: 1rem;
+ border-left: 2px solid #ddd;
+}
+
+nav.sitemap li > ul li::before {
+ content: "→ ";
+ color: #999;
+}
diff --git a/src/routes/index.js b/src/routes/index.js
index 1f7d8e3..ef04402 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -4,16 +4,18 @@
const getBaseContext = require("../utils/baseContext");
const analytics = require("./analytics");
+const robots = require("./robots");
router.post("/track", analytics);
const contact = require("./contact");
-const site_map = require("./site-map");
+const sitemap = require("./sitemap");
const post = require("./post");
const pages = require("./pages");
+router.use(robots);
router.use(contact);
-router.use(site_map);
+router.use(sitemap);
router.use(pages);
router.get("/post/:year/:month/:name", post);
diff --git a/src/routes/robots.js b/src/routes/robots.js
new file mode 100644
index 0000000..5831af9
--- /dev/null
+++ b/src/routes/robots.js
@@ -0,0 +1,16 @@
+const express = require("express");
+const router = express.Router();
+
+router.get("/robots.txt", (req, res) => {
+ const robotsTxt = `
+User-agent: *
+Disallow:
+
+Sitemap: ${req.protocol}://${req.get("host")}/sitemap.xml
+`.trim();
+
+ res.type("text/plain");
+ res.send(robotsTxt);
+});
+
+module.exports = router;
diff --git a/src/routes/site-map.js b/src/routes/site-map.js
deleted file mode 100644
index 81931f7..0000000
--- a/src/routes/site-map.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// src/routes/site-map.js
-const express = require("express");
-const router = express.Router();
-const getBaseContext = require("../utils/baseContext");
-
-router.get("/site-map", async (req, res) => {
- const context = await getBaseContext({
- title: "Site Map",
- });
- res.render("pages/site-map.handlebars", context);
-});
-
-module.exports = router;
diff --git a/src/routes/sitemap.js b/src/routes/sitemap.js
new file mode 100644
index 0000000..61acad1
--- /dev/null
+++ b/src/routes/sitemap.js
@@ -0,0 +1,51 @@
+// src/routes/sitemap.js
+const express = require("express");
+const router = express.Router();
+const fs = require("fs");
+const path = require("path");
+const Handlebars = require("handlebars");
+const getBaseContext = require("../utils/baseContext");
+const sitemapService = require("../services/sitemapService");
+
+// Precompile XML template once
+const xmlTplSrc = fs.readFileSync(
+ path.resolve(__dirname, "../views/pages/sitemap-xml.handlebars"),
+ "utf-8"
+);
+const xmlTpl = Handlebars.compile(xmlTplSrc);
+
+// function flatten(entries, out = []) {
+// for (const e of entries) {
+// if (e.loc) out.push(e.loc);
+// if (Array.isArray(e.children)) flatten(e.children, out);
+// }
+// return out;
+// }
+
+// HTML sitemap page
+router.get("/sitemap", async (req, res) => {
+ const context = await getBaseContext({
+ title: "Site Map",
+ sitemap: await sitemapService.getCompleteSitemap(),
+ });
+ res.render("pages/sitemap", context);
+});
+
+// XML sitemap endpoint
+router.get("/sitemap.xml", async (req, res) => {
+ const urls = await sitemapService.getAllUrls();
+ const baseUrl = `${req.protocol}://${req.get("host")}`;
+
+ // Format URLs for XML template
+ const formattedUrls = urls.map((url) => ({
+ loc: `${baseUrl}${url.loc}`,
+ lastmod: url.lastmod,
+ changefreq: url.changefreq,
+ priority: url.priority,
+ }));
+
+ const xml = xmlTpl({ urls: formattedUrls, baseUrl });
+ res.type("application/xml").send(xml);
+});
+
+module.exports = router;
diff --git a/src/services/sitemapService.js b/src/services/sitemapService.js
new file mode 100644
index 0000000..7e75e5e
--- /dev/null
+++ b/src/services/sitemapService.js
@@ -0,0 +1,91 @@
+// src/services/sitemapService.js
+const path = require("path");
+const fs = require("fs").promises;
+const getPostsMenu = require("./postsMenuService");
+
+class SitemapService {
+ constructor() {
+ this.staticSitemapPath = path.resolve(
+ __dirname,
+ "../../content/sitemap.json"
+ );
+ this.postsPath = path.join(__dirname, "../../content/posts");
+ }
+
+ async getStaticPages() {
+ try {
+ const data = await fs.readFile(this.staticSitemapPath, "utf-8");
+ return JSON.parse(data);
+ } catch (error) {
+ console.warn("Could not load static sitemap.json, using empty array");
+ return [];
+ }
+ }
+
+ async getBlogPostUrls() {
+ const menu = await getPostsMenu(this.postsPath);
+ const urls = [];
+
+ for (const yearData of menu) {
+ for (const monthData of yearData.months) {
+ for (const post of monthData.posts) {
+ urls.push({
+ loc: `/blog/${post.year}/${post.month}/${post.slug}`,
+ lastmod: post.date
+ ? new Date(post.date).toISOString().split("T")[0]
+ : null,
+ changefreq: "monthly",
+ priority: "0.7",
+ });
+ }
+ }
+ }
+
+ return urls;
+ }
+
+ async getCompleteSitemap() {
+ const [staticPages, blogUrls] = await Promise.all([
+ this.getStaticPages(),
+ this.getBlogPostUrls(),
+ ]);
+
+ // Add blog posts as a section in the sitemap
+ const blogSection = {
+ title: "Blog Posts",
+ children: blogUrls.map((url) => ({
+ loc: url.loc,
+ title: url.loc.split("/").pop().replace(/-/g, " "), // Convert slug to title
+ lastmod: url.lastmod,
+ changefreq: url.changefreq,
+ priority: url.priority,
+ })),
+ };
+
+ return [...staticPages, blogSection];
+ }
+
+ async getAllUrls() {
+ const sitemap = await this.getCompleteSitemap();
+ return this.flatten(sitemap);
+ }
+
+ flatten(entries, out = []) {
+ for (const entry of entries) {
+ if (entry.loc) {
+ out.push({
+ loc: entry.loc,
+ lastmod: entry.lastmod,
+ changefreq: entry.changefreq || "monthly",
+ priority: entry.priority || "0.5",
+ });
+ }
+ if (Array.isArray(entry.children)) {
+ this.flatten(entry.children, out);
+ }
+ }
+ return out;
+ }
+}
+
+module.exports = new SitemapService();
diff --git a/src/views/pages/site-map.handlebars b/src/views/pages/site-map.handlebars
deleted file mode 100644
index 977ff3b..0000000
--- a/src/views/pages/site-map.handlebars
+++ /dev/null
@@ -1,26 +0,0 @@
-{{#section "styles"}}
-
-{{/section}}
-
-
diff --git a/src/views/pages/sitemap-xml.handlebars b/src/views/pages/sitemap-xml.handlebars
new file mode 100644
index 0000000..ef377fe
--- /dev/null
+++ b/src/views/pages/sitemap-xml.handlebars
@@ -0,0 +1,11 @@
+
+