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 } }