feat: 增强维护模式和审计页面功能,优化构建流程
All checks were successful
docker-images / resolve-build-targets (push) Successful in 4s
ui-regression / playwright-regression (push) Successful in 5m55s
docker-images / build-and-push (admin) (push) Successful in 54s
docker-images / build-and-push (backend) (push) Successful in 4s
docker-images / build-and-push (frontend) (push) Successful in 1m8s
docker-images / submit-indexnow (push) Successful in 15s

This commit is contained in:
2026-04-03 01:33:24 +08:00
parent 9665c933b5
commit 27d0827f3e
10 changed files with 208 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
import { createServer } from 'node:http'
import { randomUUID } from 'node:crypto'
import { createHash, randomUUID } from 'node:crypto'
const PORT = Number(process.env.PLAYWRIGHT_MOCK_PORT || 5159)
const FRONTEND_ORIGIN =
@@ -777,6 +777,8 @@ function createSiteSettings() {
description: '适合文章阅读时循环播放的轻氛围曲。',
},
],
maintenance_mode_enabled: false,
maintenance_access_code: null,
ai_enabled: true,
paragraph_comments_enabled: true,
comment_verification_mode: 'captcha',
@@ -837,6 +839,13 @@ function createSiteSettings() {
}
}
function maintenanceAccessTokenFromSecret(secret) {
return createHash('sha256')
.update('termi-maintenance-access:v1:')
.update(String(secret))
.digest('hex')
}
function parseHeadingAndDescription(markdown, fallbackTitle = 'Untitled') {
const normalized = String(markdown || '').replace(/\r\n/g, '\n').trim()
const titleMatch = normalized.match(/^#\s+(.+)$/m)
@@ -1941,6 +1950,46 @@ const server = createServer(async (req, res) => {
return
}
if (pathname === '/api/site_settings/maintenance/status' && req.method === 'POST') {
const { json: payload } = await parseRequest(req)
const enabled = Boolean(state.site_settings.maintenance_mode_enabled)
const secret = normalizeText(state.site_settings.maintenance_access_code)
const expectedToken = secret ? maintenanceAccessTokenFromSecret(secret) : null
const accessToken =
normalizeText(payload.accessToken) ||
normalizeText(payload.access_token)
json(res, 200, {
maintenance_mode_enabled: enabled,
access_granted: !enabled || Boolean(expectedToken && accessToken === expectedToken),
})
return
}
if (pathname === '/api/site_settings/maintenance/verify' && req.method === 'POST') {
const { json: payload } = await parseRequest(req)
const enabled = Boolean(state.site_settings.maintenance_mode_enabled)
const secret = normalizeText(state.site_settings.maintenance_access_code)
const code = normalizeText(payload.code)
if (!enabled) {
json(res, 200, {
maintenance_mode_enabled: false,
access_granted: true,
access_token: null,
})
return
}
const accessGranted = Boolean(secret && code && code === secret)
json(res, 200, {
maintenance_mode_enabled: true,
access_granted: accessGranted,
access_token: accessGranted ? maintenanceAccessTokenFromSecret(secret) : null,
})
return
}
if (pathname === '/api/site_settings/home' && req.method === 'GET') {
json(res, 200, getHomePayload())
return
@@ -2686,6 +2735,8 @@ const server = createServer(async (req, res) => {
techStack: 'tech_stack',
musicPlaylist: 'music_playlist',
aiEnabled: 'ai_enabled',
maintenanceModeEnabled: 'maintenance_mode_enabled',
maintenanceAccessCode: 'maintenance_access_code',
paragraphCommentsEnabled: 'paragraph_comments_enabled',
commentVerificationMode: 'comment_verification_mode',
commentTurnstileEnabled: 'comment_turnstile_enabled',