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,60 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const commentService = require('../services/comments');
|
||||
const contentService = require('../services/content');
|
||||
const pluginManager = require('../services/plugin-manager');
|
||||
|
||||
router.get('/api/comments/:slug', (req, res) => {
|
||||
const comments = commentService.getCommentsForPost(req.params.slug);
|
||||
res.json({ comments });
|
||||
});
|
||||
|
||||
router.post('/api/comments', async (req, res) => {
|
||||
try {
|
||||
const { postSlug, authorName, authorEmail, authorWebsite, body, honeypot } = req.body;
|
||||
|
||||
if (!postSlug || !authorName || !body) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
||||
|
||||
const newComment = commentService.addComment({
|
||||
postSlug,
|
||||
authorName,
|
||||
authorEmail,
|
||||
authorWebsite,
|
||||
body,
|
||||
honeypot,
|
||||
ip: clientIp
|
||||
});
|
||||
|
||||
// Invalidate page cache so new comment and admin views update immediately
|
||||
const pageCache = require('../middleware/cache');
|
||||
pageCache.clear();
|
||||
|
||||
const post = contentService.getPost(postSlug);
|
||||
// Fire plugin lifecycle hook asynchronously
|
||||
pluginManager.triggerCommentCreated(newComment, post).catch(err => {
|
||||
console.error('[CommentRoute] Error in onCommentCreated plugin hook:', err);
|
||||
});
|
||||
|
||||
// If submitted via AJAX, return JSON
|
||||
if (req.xhr || req.headers.accept.includes('json')) {
|
||||
return res.json({
|
||||
success: true,
|
||||
comment: newComment,
|
||||
message: newComment.approved === 1 ? 'Comment posted!' : 'Your comment is awaiting moderation.'
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise redirect back to post comments anchor with flash message
|
||||
const msg = newComment.approved === 1 ? 'comment_posted' : 'comment_pending';
|
||||
res.redirect(`/${postSlug}?comment_status=${msg}#comments`);
|
||||
} catch (err) {
|
||||
console.error('[CommentRoute] Error saving comment:', err);
|
||||
res.status(500).json({ error: 'Failed to post comment' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user