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
+120
View File
@@ -0,0 +1,120 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const { CONTENT_DIR, loadSiteConfig } = require('./config');
const contentService = require('./services/content');
const commentService = require('./services/comments');
const pluginManager = require('./services/plugin-manager');
const watcherService = require('./services/watcher');
const seoMiddleware = require('./middleware/seo');
const pageCache = require('./middleware/cache');
const { notFoundHandler, serverErrorHandler } = require('./middleware/error-handler');
// Route modules
const homeRouter = require('./routes/home');
const blogsRouter = require('./routes/blogs');
const searchRouter = require('./routes/search');
const feedRouter = require('./routes/feed');
const sitemapRouter = require('./routes/sitemap');
const commentsRouter = require('./routes/comments');
const postRouter = require('./routes/post');
const pagesRouter = require('./routes/pages');
const adminRouter = require('./routes/admin');
const app = express();
const PORT = process.env.PORT || 3000;
// View engine setup
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../views'));
// Parse JSON & URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Load site configuration
const siteConfig = loadSiteConfig();
// Serve built-in skeleton static assets (CSS, JS, fonts)
app.use(express.static(path.join(__dirname, '../public')));
// Serve Persistent Volume static directories
const pvStaticDir = path.join(CONTENT_DIR, 'static');
if (fs.existsSync(pvStaticDir)) {
app.use(express.static(pvStaticDir));
}
// Serve PV custom CSS if present
const pvCssDir = path.join(CONTENT_DIR, 'css');
if (fs.existsSync(pvCssDir)) {
app.use('/css/custom', express.static(pvCssDir));
}
// Serve post assets (e.g. cover images) at /posts-assets/<slug>/...
const pvPostsDir = path.join(CONTENT_DIR, 'posts');
if (fs.existsSync(pvPostsDir)) {
app.use('/posts-assets', express.static(pvPostsDir));
}
// Initialize services
commentService.init(CONTENT_DIR);
pluginManager.loadPlugins(CONTENT_DIR, app, siteConfig);
contentService.init(CONTENT_DIR);
watcherService.start(CONTENT_DIR, app);
// Inject SEO locals & plugin hook helper
app.use(seoMiddleware(siteConfig));
app.use((req, res, next) => {
res.locals.pluginHook = (hookName, extraContext = {}) => {
return pluginManager.renderHook(hookName, {
req,
res,
site: res.locals.site,
...extraContext
});
};
next();
});
// Super Page Cache micro-caching
app.use(pageCache.middleware());
// Mount routes in order of specificity
app.use('/', adminRouter);
app.use('/', homeRouter);
app.use('/', blogsRouter);
app.use('/', searchRouter);
app.use('/', feedRouter);
app.use('/', sitemapRouter);
app.use('/', commentsRouter);
// Specific blog post routes (/:slug, /:slug/:lang)
app.use('/', postRouter);
// Fallback standalone pages (/:pageSlug from pages/)
app.use('/', pagesRouter);
// Error handlers
app.use(notFoundHandler);
app.use(serverErrorHandler);
if (require.main === module) {
const server = app.listen(PORT, () => {
console.log(`=========================================`);
console.log(` Pengzhan.dev Web Server running at:`);
console.log(` http://localhost:${PORT}`);
console.log(` Content directory: ${CONTENT_DIR}`);
console.log(`=========================================`);
});
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
watcherService.close();
server.close(() => {
console.log('HTTP server closed');
});
});
}
module.exports = app;