Files
sori.studio/server/utils/analytics-pageview-input.js
zenn 3623305119 v1.3.3: 자체 최소 통계 및 스플래시 localStorage 정리
- 일별 익명 방문자 해시·사이트/게시물 통계(030 마이그레이션)
- POST /api/analytics/pageview, 관리자 analytics API, 클라이언트 트래커
- 관리자 대시보드 통계 카드·인기 게시물 Top 5
- 스플래시: SITE_BRAND_LOGO_TEXT localStorage 제거
2026-05-20 12:15:13 +09:00

69 lines
1.7 KiB
JavaScript

import { z } from 'zod'
import {
isBotUserAgent,
isTrackableAnalyticsPath,
normalizePostSlugForAnalytics
} from '../../lib/analytics.js'
import { getPostBySlug } from '../repositories/content-repository.js'
import {
createVisitorHashFromEvent,
recordAnalyticsPageview
} from '../repositories/analytics-repository.js'
const pageviewInputSchema = z.object({
path: z.string().trim().min(1).max(500),
postSlug: z.string().trim().max(200).optional().default(''),
read: z.boolean().optional().default(false)
})
/**
* 페이지뷰 추적 요청을 처리한다.
* @param {import('h3').H3Event} event - H3 이벤트
* @returns {Promise<{ ok: true }>}
*/
export const handleAnalyticsPageview = async (event) => {
const parsedBody = pageviewInputSchema.safeParse(await readBody(event))
if (!parsedBody.success) {
throw createError({
statusCode: 400,
message: '통계 요청 형식이 올바르지 않습니다.'
})
}
const body = parsedBody.data
const userAgent = String(getRequestHeader(event, 'user-agent') || '')
if (isBotUserAgent(userAgent)) {
return { ok: true }
}
if (!isTrackableAnalyticsPath(body.path)) {
return { ok: true }
}
const postSlug = normalizePostSlugForAnalytics(body.postSlug)
let postId = null
if (postSlug) {
const post = await getPostBySlug(postSlug)
if (!post) {
return { ok: true }
}
postId = post.id
}
const visitorHash = createVisitorHashFromEvent(event)
const isReadEvent = Boolean(body.read)
await recordAnalyticsPageview({
visitorHash,
postId,
recordSite: !isReadEvent,
recordView: Boolean(postId) && !isReadEvent,
recordRead: Boolean(postId) && isReadEvent
})
return { ok: true }
}