feat: initial release of pengzhan.dev personal editorial blog platform

- Editorial web platform built with Node.js, Express, EJS, and Vanilla CSS
- Markdown content engine with YAML frontmatter, bilingual variants, and hot-reload watcher
- Unified SQLite with FTS5 search indexing, query logging, and schema filters
- Centered editorial post layout with margin-docked left TOC and mobile bottom sheet
- WordPress-grade comment system with moderation dashboard and bulk operations under /guanli
- Complete SEO suite (JSON-LD structured data, RSS/Atom feeds, XML sitemap, robots.txt)
- CI/CD workflows for linting, security audits (Gitleaks), automated tests, and Docker releases
This commit is contained in:
2026-09-16 23:08:53 -07:00
parent ac2c4c09ef
commit 8c0b04db0e
126 changed files with 8515 additions and 3984 deletions
+75
View File
@@ -0,0 +1,75 @@
const express = require('express');
const router = express.Router();
const contentService = require('../services/content');
router.get('/sitemap.xml', (req, res) => {
const siteUrl = res.locals.site.url;
const posts = contentService.getAllPosts();
const pages = Array.from(contentService.pagesBySlug.values());
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
// Static / Top-level pages
const staticRoutes = ['/', '/blogs', '/search'];
for (const route of staticRoutes) {
xml += ' <url>\n';
xml += ` <loc>${siteUrl}${route}</loc>\n`;
xml += ` <changefreq>daily</changefreq>\n`;
xml += ` <priority>${route === '/' ? '1.0' : '0.8'}</priority>\n`;
xml += ' </url>\n';
}
// Standalone pages
for (const page of pages) {
if (!page.slug || page.slug === '/') continue;
const cleanSlug = page.slug.startsWith('/') ? page.slug : `/${page.slug}`;
if (cleanSlug === '/guanli' || cleanSlug.startsWith('/guanli/') || cleanSlug === '/admin' || cleanSlug.startsWith('/admin/')) {
continue;
}
xml += ' <url>\n';
xml += ` <loc>${siteUrl}${cleanSlug}</loc>\n`;
xml += ` <changefreq>weekly</changefreq>\n`;
xml += ` <priority>0.7</priority>\n`;
xml += ' </url>\n';
}
// Blog posts (including translations)
for (const post of posts) {
if (!post.slug || post.slug.startsWith('guanli') || post.slug.startsWith('admin')) continue;
xml += ' <url>\n';
xml += ` <loc>${siteUrl}/${post.slug}</loc>\n`;
xml += ` <lastmod>${post.date.toISOString().split('T')[0]}</lastmod>\n`;
xml += ` <changefreq>monthly</changefreq>\n`;
xml += ` <priority>0.9</priority>\n`;
xml += ' </url>\n';
// Bilingual URLs
if (post.availableLanguages) {
for (const lang of post.availableLanguages) {
if (lang !== post.lang && lang !== 'default') {
xml += ' <url>\n';
xml += ` <loc>${siteUrl}/${post.slug}/${lang}</loc>\n`;
xml += ` <lastmod>${post.date.toISOString().split('T')[0]}</lastmod>\n`;
xml += ` <changefreq>monthly</changefreq>\n`;
xml += ` <priority>0.8</priority>\n`;
xml += ' </url>\n';
}
}
}
}
xml += '</urlset>';
res.header('Content-Type', 'application/xml; charset=utf-8');
res.send(xml);
});
router.get('/robots.txt', (req, res) => {
const siteUrl = res.locals.site.url;
const content = `User-agent: *\nDisallow: /admin/\nAllow: /\n\nSitemap: ${siteUrl}/sitemap.xml\n`;
res.header('Content-Type', 'text/plain; charset=utf-8');
res.send(content);
});
module.exports = router;