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:
@@ -0,0 +1,52 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const contentService = require('../services/content');
|
||||
const commentService = require('../services/comments');
|
||||
|
||||
function renderPost(req, res, next, slug, lang) {
|
||||
const post = contentService.getPost(slug, lang);
|
||||
if (!post) {
|
||||
return next(); // Fall through to standalone pages or 404
|
||||
}
|
||||
|
||||
const { prev, next: nextPost } = contentService.getAdjacentPosts(slug);
|
||||
const comments = commentService.getCommentsForPost(slug);
|
||||
|
||||
// Set SEO metadata for post
|
||||
res.locals.seo = {
|
||||
title: `${post.title} - ${res.locals.site.title}`,
|
||||
description: post.excerpt || res.locals.site.tagline,
|
||||
canonicalUrl: `${res.locals.site.url}/${post.slug}${post.lang !== 'en' && !post.isDefault ? `/${post.lang}` : ''}`,
|
||||
ogImage: post.cover ? (post.cover.startsWith('http') ? post.cover : `${res.locals.site.url}${post.cover}`) : res.locals.seo.ogImage,
|
||||
type: 'article',
|
||||
publishedTime: post.date.toISOString(),
|
||||
author: post.author
|
||||
};
|
||||
|
||||
res.render('pages/post', {
|
||||
title: post.title,
|
||||
post,
|
||||
currentLang: lang || post.lang,
|
||||
comments,
|
||||
commentStatus: req.query.comment_status,
|
||||
prevPost: prev,
|
||||
nextPost
|
||||
});
|
||||
}
|
||||
|
||||
// Route for bilingual variant: /<post-slug>/<lang> (e.g. /my-post/zh)
|
||||
router.get('/:slug/:lang([a-zA-Z]{2,5})', (req, res, next) => {
|
||||
renderPost(req, res, next, req.params.slug, req.params.lang);
|
||||
});
|
||||
|
||||
// Route for default language: /<post-slug>
|
||||
router.get('/:slug', (req, res, next) => {
|
||||
// Exclude reserved top-level prefixes if any
|
||||
const reserved = ['blogs', 'search', 'quick-links', 'feed', 'rss.xml', 'sitemap.xml', 'robots.txt', 'api', 'plugins', 'public', 'posts-assets', 'guanli', 'admin'];
|
||||
if (reserved.includes(req.params.slug)) {
|
||||
return next();
|
||||
}
|
||||
renderPost(req, res, next, req.params.slug, null);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user