Files
sori.studio/server/utils/analytics-pageview-input.js

96 lines
2.5 KiB
JavaScript

import { z } from 'zod'
import {
isBotUserAgent,
isTrackableAnalyticsPath,
normalizePageSlugForAnalytics,
normalizePostSlugForAnalytics
} from '../../lib/analytics.js'
import {
classifyAnalyticsDevice,
classifyAnalyticsTrafficSource
} from '../../lib/analytics-traffic.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(''),
referrer: z.string().trim().max(1000).optional().default(''),
currentUrl: z.string().trim().max(1000).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)
const traffic = {
...classifyAnalyticsTrafficSource({
referrer: body.referrer,
currentUrl: body.currentUrl
}),
...classifyAnalyticsDevice(userAgent)
}
await recordAnalyticsPageview({
visitorHash,
postId,
pageId,
traffic,
recordSite: !isReadEvent,
recordView: (Boolean(postId) || Boolean(pageId)) && !isReadEvent,
recordRead: Boolean(postId) && isReadEvent
})
return { ok: true }
}