Files
sori.studio/server/middleware/html-page-renderer.js
zenn de767095ba v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.
세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 11:05:40 +09:00

57 lines
1.7 KiB
JavaScript

import { getMethod, getRequestHeader, getRequestURL, setResponseHeader } from 'h3'
import { isBotUserAgent } from '../../lib/analytics'
import {
createVisitorHashFromEvent,
recordAnalyticsPageview
} from '../repositories/analytics-repository'
import { getPageBySlug } from '../repositories/content-repository'
import { shouldExcludeAdminAnalytics } from '../utils/analytics-admin-exclusion'
import { applySecurityHeaders } from '../utils/security-headers.js'
/**
* 공개 페이지가 HTML 문서 모드일 때 Nuxt 렌더링 대신 원문 HTML을 응답한다.
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<string | void>} HTML 문서 또는 다음 핸들러 진행
*/
export default defineEventHandler(async (event) => {
const method = getMethod(event)
if (method !== 'GET' && method !== 'HEAD') {
return
}
const pathname = getRequestURL(event).pathname
const match = pathname.match(/^\/pages\/([^/]+)\/?$/)
if (!match) {
return
}
const slug = decodeURIComponent(match[1])
const page = await getPageBySlug(slug)
if (page?.renderMode !== 'html_document') {
return
}
if (
method === 'GET'
&& !isBotUserAgent(String(getRequestHeader(event, 'user-agent') || ''))
&& !shouldExcludeAdminAnalytics(event)
) {
await recordAnalyticsPageview({
visitorHash: createVisitorHashFromEvent(event),
pageId: page.id,
recordSite: true,
recordView: true,
recordRead: false
})
}
setResponseHeader(event, 'content-type', 'text/html; charset=utf-8')
setResponseHeader(event, 'cache-control', 'no-cache')
applySecurityHeaders(event, { enforceCsp: false })
return page.content || ''
})