82 lines
2.1 KiB
JavaScript
82 lines
2.1 KiB
JavaScript
import { z } from 'zod'
|
|
import {
|
|
isBotUserAgent,
|
|
isTrackableAnalyticsPath,
|
|
normalizePageSlugForAnalytics,
|
|
normalizePostSlugForAnalytics
|
|
} from '../../lib/analytics.js'
|
|
import { getPageBySlug, 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(''),
|
|
pageSlug: 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)
|
|
const pageSlug = normalizePageSlugForAnalytics(body.pageSlug)
|
|
let postId = null
|
|
let pageId = null
|
|
|
|
if (postSlug) {
|
|
const post = await getPostBySlug(postSlug)
|
|
if (!post) {
|
|
return { ok: true }
|
|
}
|
|
postId = post.id
|
|
}
|
|
|
|
if (!postId && pageSlug) {
|
|
const page = await getPageBySlug(pageSlug)
|
|
if (!page) {
|
|
return { ok: true }
|
|
}
|
|
pageId = page.id
|
|
}
|
|
|
|
const visitorHash = createVisitorHashFromEvent(event)
|
|
const isReadEvent = Boolean(body.read)
|
|
|
|
await recordAnalyticsPageview({
|
|
visitorHash,
|
|
postId,
|
|
pageId,
|
|
recordSite: !isReadEvent,
|
|
recordView: (Boolean(postId) || Boolean(pageId)) && !isReadEvent,
|
|
recordRead: Boolean(postId) && isReadEvent
|
|
})
|
|
|
|
return { ok: true }
|
|
}
|