인기 페이지 통계와 추천 사이트 메타데이터 추가 v1.5.9
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { getMethod, getRequestURL, setResponseHeader } from 'h3'
|
||||
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'
|
||||
|
||||
/**
|
||||
@@ -27,6 +32,16 @@ export default defineEventHandler(async (event) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (method === 'GET' && !isBotUserAgent(String(getRequestHeader(event, 'user-agent') || ''))) {
|
||||
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')
|
||||
|
||||
|
||||
@@ -60,6 +60,21 @@ const ensurePostDailyRow = async (sql, day, postId) => {
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* 일별 페이지 통계 행을 보장한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} day - YYYY-MM-DD
|
||||
* @param {string} pageId - 페이지 ID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const ensurePageDailyRow = async (sql, day, pageId) => {
|
||||
await sql`
|
||||
INSERT INTO page_analytics_daily (day, page_id)
|
||||
VALUES (${day}, ${pageId})
|
||||
ON CONFLICT (day, page_id) DO NOTHING
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* 사이트 방문자를 등록하고 신규 방문자면 true를 반환한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
@@ -97,9 +112,28 @@ const registerPostVisitor = async (sql, day, postId, visitorHash) => {
|
||||
return Boolean(rows[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 방문자를 등록하고 신규 방문자면 true를 반환한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} day - YYYY-MM-DD
|
||||
* @param {string} pageId - 페이지 ID
|
||||
* @param {string} visitorHash - 방문자 해시
|
||||
* @returns {Promise<boolean>} 신규 방문자 여부
|
||||
*/
|
||||
const registerPageVisitor = async (sql, day, pageId, visitorHash) => {
|
||||
const rows = await sql`
|
||||
INSERT INTO analytics_daily_visitors (day, scope, page_id, visitor_hash)
|
||||
VALUES (${day}, 'page', ${pageId}, ${visitorHash})
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
return Boolean(rows[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지뷰·읽음 이벤트를 기록한다.
|
||||
* @param {{ visitorHash: string, postId?: string | null, recordSite?: boolean, recordView?: boolean, recordRead?: boolean }} input - 기록 입력
|
||||
* @param {{ visitorHash: string, postId?: string | null, pageId?: string | null, recordSite?: boolean, recordView?: boolean, recordRead?: boolean }} input - 기록 입력
|
||||
* @returns {Promise<{ ok: true }>}
|
||||
*/
|
||||
export const recordAnalyticsPageview = async (input) => {
|
||||
@@ -112,6 +146,7 @@ export const recordAnalyticsPageview = async (input) => {
|
||||
const day = getAnalyticsDayKey()
|
||||
const visitorHash = input.visitorHash
|
||||
const postId = input.postId || null
|
||||
const pageId = input.pageId || null
|
||||
const recordSite = input.recordSite !== false
|
||||
const recordView = Boolean(input.recordView)
|
||||
const recordRead = Boolean(input.recordRead)
|
||||
@@ -131,6 +166,21 @@ export const recordAnalyticsPageview = async (input) => {
|
||||
}
|
||||
|
||||
if (!postId) {
|
||||
if (pageId && recordView) {
|
||||
await ensurePageDailyRow(sql, day, pageId)
|
||||
|
||||
const isNewPageVisitor = await registerPageVisitor(sql, day, pageId, visitorHash)
|
||||
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET
|
||||
views = views + 1,
|
||||
visitors = visitors + ${isNewPageVisitor ? 1 : 0}
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
}
|
||||
|
||||
await purgeAnalyticsRetention(sql)
|
||||
return { ok: true }
|
||||
}
|
||||
@@ -279,9 +329,57 @@ const incrementPostScrollBuckets = async (sql, day, postId, columns) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 스크롤 구간 카운터를 증가시킨다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} day - YYYY-MM-DD
|
||||
* @param {string} pageId - 페이지 ID
|
||||
* @param {Array<'scroll_25' | 'scroll_50' | 'scroll_75' | 'scroll_100'>} columns - 구간 컬럼
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const incrementPageScrollBuckets = async (sql, day, pageId, columns) => {
|
||||
if (!columns.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await ensurePageDailyRow(sql, day, pageId)
|
||||
|
||||
for (const column of columns) {
|
||||
if (column === 'scroll_25') {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET scroll_25 = scroll_25 + 1
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
} else if (column === 'scroll_50') {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET scroll_50 = scroll_50 + 1
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
} else if (column === 'scroll_75') {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET scroll_75 = scroll_75 + 1
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
} else if (column === 'scroll_100') {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET scroll_100 = scroll_100 + 1
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* heartbeat·체류·스크롤·실시간 세션을 기록한다.
|
||||
* @param {{ event: import('h3').H3Event, sessionHash: string, path: string, postId?: string | null, postSlug?: string, durationSeconds: number, maxScrollRatio: number }} input - 기록 입력
|
||||
* @param {{ event: import('h3').H3Event, sessionHash: string, path: string, postId?: string | null, postSlug?: string, pageId?: string | null, pageSlug?: string, durationSeconds: number, maxScrollRatio: number }} input - 기록 입력
|
||||
* @returns {Promise<{ ok: true }>}
|
||||
*/
|
||||
export const recordAnalyticsHeartbeat = async (input) => {
|
||||
@@ -296,6 +394,8 @@ export const recordAnalyticsHeartbeat = async (input) => {
|
||||
const path = input.path
|
||||
const postId = input.postId || null
|
||||
const postSlug = input.postSlug || ''
|
||||
const pageId = input.pageId || null
|
||||
const pageSlug = input.pageSlug || ''
|
||||
const durationSeconds = clampAnalyticsDurationSeconds(input.durationSeconds)
|
||||
const maxScrollRatio = clampAnalyticsScrollRatio(input.maxScrollRatio)
|
||||
const memberSession = getMemberSession(input.event)
|
||||
@@ -320,6 +420,8 @@ export const recordAnalyticsHeartbeat = async (input) => {
|
||||
path,
|
||||
post_id,
|
||||
post_slug,
|
||||
page_id,
|
||||
page_slug,
|
||||
duration_seconds,
|
||||
max_scroll_ratio
|
||||
)
|
||||
@@ -329,6 +431,8 @@ export const recordAnalyticsHeartbeat = async (input) => {
|
||||
${path},
|
||||
${postId},
|
||||
${postSlug},
|
||||
${pageId},
|
||||
${pageSlug},
|
||||
${durationSeconds},
|
||||
${maxScrollRatio}
|
||||
)
|
||||
@@ -338,6 +442,8 @@ export const recordAnalyticsHeartbeat = async (input) => {
|
||||
path = EXCLUDED.path,
|
||||
post_id = EXCLUDED.post_id,
|
||||
post_slug = EXCLUDED.post_slug,
|
||||
page_id = EXCLUDED.page_id,
|
||||
page_slug = EXCLUDED.page_slug,
|
||||
duration_seconds = GREATEST(analytics_active_sessions.duration_seconds, EXCLUDED.duration_seconds),
|
||||
max_scroll_ratio = GREATEST(analytics_active_sessions.max_scroll_ratio, EXCLUDED.max_scroll_ratio),
|
||||
last_seen_at = now()
|
||||
@@ -391,6 +497,33 @@ export const recordAnalyticsHeartbeat = async (input) => {
|
||||
await incrementPostScrollBuckets(sql, day, postId, scrollBuckets)
|
||||
}
|
||||
|
||||
if (pageId && (durationDelta > 0 || scrollBuckets.length)) {
|
||||
await ensurePageDailyRow(sql, day, pageId)
|
||||
|
||||
if (durationDelta > 0) {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET total_engaged_seconds = total_engaged_seconds + ${durationDelta}
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
|
||||
const wasPageEngaged = previousDuration >= ANALYTICS_ENGAGED_MIN_SECONDS
|
||||
const isPageEngaged = durationSeconds >= ANALYTICS_ENGAGED_MIN_SECONDS
|
||||
|
||||
if (!wasPageEngaged && isPageEngaged) {
|
||||
await sql`
|
||||
UPDATE page_analytics_daily
|
||||
SET engaged_views = engaged_views + 1
|
||||
WHERE day = ${day}
|
||||
AND page_id = ${pageId}
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
await incrementPageScrollBuckets(sql, day, pageId, scrollBuckets)
|
||||
}
|
||||
|
||||
await purgeStaleActiveSessions(sql)
|
||||
await purgeAnalyticsRetention(sql)
|
||||
|
||||
@@ -575,15 +708,18 @@ export const getAnalyticsActiveSessions = async (options = {}) => {
|
||||
analytics_active_sessions.session_hash,
|
||||
analytics_active_sessions.path,
|
||||
analytics_active_sessions.post_slug,
|
||||
analytics_active_sessions.page_slug,
|
||||
analytics_active_sessions.duration_seconds,
|
||||
analytics_active_sessions.max_scroll_ratio,
|
||||
analytics_active_sessions.last_seen_at,
|
||||
posts.title AS post_title,
|
||||
pages.title AS page_title,
|
||||
users.id AS user_id,
|
||||
users.username,
|
||||
users.avatar_url
|
||||
FROM analytics_active_sessions
|
||||
LEFT JOIN posts ON posts.id = analytics_active_sessions.post_id
|
||||
LEFT JOIN pages ON pages.id = analytics_active_sessions.page_id
|
||||
LEFT JOIN users ON users.id = analytics_active_sessions.user_id
|
||||
WHERE analytics_active_sessions.last_seen_at >= now() - (${ANALYTICS_ACTIVE_SESSION_TTL_SECONDS} * interval '1 second')
|
||||
ORDER BY analytics_active_sessions.last_seen_at DESC
|
||||
@@ -594,7 +730,9 @@ export const getAnalyticsActiveSessions = async (options = {}) => {
|
||||
sessionHash: row.session_hash,
|
||||
path: row.path,
|
||||
postSlug: row.post_slug || '',
|
||||
pageSlug: row.page_slug || '',
|
||||
postTitle: row.post_title || '',
|
||||
pageTitle: row.page_title || '',
|
||||
durationSeconds: Number(row.duration_seconds || 0),
|
||||
maxScrollRatio: Number(row.max_scroll_ratio || 0),
|
||||
lastSeenAt: row.last_seen_at ? new Date(row.last_seen_at).toISOString() : null,
|
||||
@@ -668,3 +806,61 @@ export const getAnalyticsTopPosts = async (options = {}) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 인기 페이지 통계를 조회한다.
|
||||
* @param {{ days?: number, limit?: number }} [options] - 조회 옵션
|
||||
* @returns {Promise<Array<Object>>} 인기 페이지 목록
|
||||
*/
|
||||
export const getAnalyticsTopPages = async (options = {}) => {
|
||||
const sql = getPostgresClient()
|
||||
const days = Math.min(Math.max(Number(options.days) || 30, 1), ANALYTICS_CHART_MAX_DAYS)
|
||||
const limit = Math.min(Math.max(Number(options.limit) || 5, 1), 20)
|
||||
|
||||
if (!sql) {
|
||||
return []
|
||||
}
|
||||
|
||||
const today = getAnalyticsDayKey()
|
||||
const rangeStartDay = getAnalyticsDayBefore(today, days - 1)
|
||||
await purgeAnalyticsRetention(sql)
|
||||
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
pages.id,
|
||||
pages.title,
|
||||
pages.slug,
|
||||
COALESCE(SUM(page_analytics_daily.views), 0)::int AS views,
|
||||
COALESCE(SUM(page_analytics_daily.visitors), 0)::int AS visitors,
|
||||
COALESCE(SUM(page_analytics_daily.engaged_views), 0)::int AS engaged_views,
|
||||
COALESCE(SUM(page_analytics_daily.total_engaged_seconds), 0)::int AS total_engaged_seconds,
|
||||
COALESCE(SUM(page_analytics_daily.scroll_50), 0)::int AS scroll_50,
|
||||
COALESCE(SUM(page_analytics_daily.scroll_75), 0)::int AS scroll_75,
|
||||
COALESCE(SUM(page_analytics_daily.scroll_100), 0)::int AS scroll_100
|
||||
FROM page_analytics_daily
|
||||
INNER JOIN pages ON pages.id = page_analytics_daily.page_id
|
||||
WHERE page_analytics_daily.day >= ${rangeStartDay}::date
|
||||
GROUP BY pages.id, pages.title, pages.slug
|
||||
ORDER BY views DESC, pages.updated_at DESC NULLS LAST
|
||||
LIMIT ${limit}
|
||||
`
|
||||
|
||||
return rows.map((row) => {
|
||||
const engagedViews = Number(row.engaged_views || 0)
|
||||
const totalEngagedSeconds = Number(row.total_engaged_seconds || 0)
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
slug: row.slug,
|
||||
views: Number(row.views || 0),
|
||||
visitors: Number(row.visitors || 0),
|
||||
avgEngagedSeconds: engagedViews > 0
|
||||
? Math.round(totalEngagedSeconds / engagedViews)
|
||||
: 0,
|
||||
scroll50: Number(row.scroll_50 || 0),
|
||||
scroll75: Number(row.scroll_75 || 0),
|
||||
scroll100: Number(row.scroll_100 || 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ const mapNavigationItemRow = (row) => ({
|
||||
id: row.id,
|
||||
label: row.label,
|
||||
url: row.url,
|
||||
descriptionText: row.description_text || '',
|
||||
thumbnailUrl: row.thumbnail_url || '',
|
||||
location: row.location,
|
||||
sortOrder: row.sort_order,
|
||||
isVisible: row.is_visible,
|
||||
@@ -1046,6 +1048,8 @@ export const getPublicNavigation = async () => {
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
url: item.url,
|
||||
descriptionText: item.descriptionText,
|
||||
thumbnailUrl: item.thumbnailUrl,
|
||||
isVisible: item.isVisible
|
||||
}))
|
||||
}
|
||||
@@ -1075,6 +1079,8 @@ export const updateNavigationItems = async (items) => {
|
||||
id,
|
||||
label,
|
||||
url,
|
||||
description_text,
|
||||
thumbnail_url,
|
||||
location,
|
||||
sort_order,
|
||||
is_visible,
|
||||
@@ -1085,6 +1091,8 @@ export const updateNavigationItems = async (items) => {
|
||||
${item.id},
|
||||
${item.label},
|
||||
${item.url},
|
||||
${item.descriptionText || ''},
|
||||
${item.thumbnailUrl || ''},
|
||||
${item.location},
|
||||
${item.sortOrder},
|
||||
${item.isVisible},
|
||||
|
||||
17
server/routes/admin/api/analytics/pages.get.js
Normal file
17
server/routes/admin/api/analytics/pages.get.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { requireAdminSession } from '../../../../utils/admin-auth.js'
|
||||
import { getAnalyticsTopPages } from '../../../../repositories/analytics-repository.js'
|
||||
|
||||
/**
|
||||
* 관리자 인기 페이지 통계 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<Array<Object>>} 인기 페이지 목록
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAdminSession(event)
|
||||
|
||||
const query = getQuery(event)
|
||||
const days = Number(query.days) || 30
|
||||
const limit = Number(query.limit) || 5
|
||||
|
||||
return getAnalyticsTopPages({ days, limit })
|
||||
})
|
||||
@@ -45,6 +45,8 @@ export default defineEventHandler(async (event) => {
|
||||
id: row.id,
|
||||
label: row.label.trim(),
|
||||
url: row.url.trim(),
|
||||
descriptionText: row.descriptionText.trim(),
|
||||
thumbnailUrl: row.thumbnailUrl.trim(),
|
||||
location: row.location,
|
||||
sortOrder: row.sortOrder,
|
||||
isVisible: true,
|
||||
@@ -70,10 +72,16 @@ export default defineEventHandler(async (event) => {
|
||||
} catch (err) {
|
||||
const msg = err?.message != null ? String(err.message) : String(err)
|
||||
const code = err?.code != null ? String(err.code) : ''
|
||||
if (msg.includes('parent_id') || msg.includes('is_folder') || code === '42703') {
|
||||
if (
|
||||
msg.includes('parent_id') ||
|
||||
msg.includes('is_folder') ||
|
||||
msg.includes('description_text') ||
|
||||
msg.includes('thumbnail_url') ||
|
||||
code === '42703'
|
||||
) {
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
message: 'DB에 navigation_items 확장 컬럼이 없습니다. 프로젝트 루트에서 npm run db:migrate:dev 로 017_navigation_hierarchy.sql을 적용한 뒤 다시 저장하세요.'
|
||||
message: 'DB에 navigation_items 확장 컬럼이 없습니다. 프로젝트 루트에서 npm run db:migrate:dev 로 최신 마이그레이션을 적용한 뒤 다시 저장하세요.'
|
||||
})
|
||||
}
|
||||
throw err
|
||||
|
||||
@@ -4,6 +4,8 @@ export const adminNavigationItemInputSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
label: z.string().trim().min(1),
|
||||
url: z.string().trim().min(1).regex(/^(\/|https?:\/\/|#)/),
|
||||
descriptionText: z.string().trim().max(200).optional().default(''),
|
||||
thumbnailUrl: z.string().trim().max(500).optional().default(''),
|
||||
location: z.enum(['primary', 'footer', 'recommended']),
|
||||
sortOrder: z.coerce.number().int().min(0).default(0),
|
||||
isVisible: z.boolean().default(true),
|
||||
|
||||
@@ -2,9 +2,10 @@ import { z } from 'zod'
|
||||
import {
|
||||
isBotUserAgent,
|
||||
isTrackableAnalyticsPath,
|
||||
normalizePageSlugForAnalytics,
|
||||
normalizePostSlugForAnalytics
|
||||
} from '../../lib/analytics.js'
|
||||
import { getPostBySlug } from '../repositories/content-repository.js'
|
||||
import { getPageBySlug, getPostBySlug } from '../repositories/content-repository.js'
|
||||
import {
|
||||
createSessionHashFromEvent,
|
||||
recordAnalyticsHeartbeat
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
const heartbeatInputSchema = 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(''),
|
||||
clientSessionId: z.string().trim().min(8).max(120),
|
||||
durationSeconds: z.number().int().min(0).max(1800),
|
||||
maxScrollRatio: z.number().min(0).max(1)
|
||||
@@ -45,7 +47,9 @@ export const handleAnalyticsHeartbeat = async (event) => {
|
||||
}
|
||||
|
||||
const postSlug = normalizePostSlugForAnalytics(body.postSlug)
|
||||
const pageSlug = normalizePageSlugForAnalytics(body.pageSlug)
|
||||
let postId = null
|
||||
let pageId = null
|
||||
|
||||
if (postSlug) {
|
||||
const post = await getPostBySlug(postSlug)
|
||||
@@ -55,6 +59,14 @@ export const handleAnalyticsHeartbeat = async (event) => {
|
||||
postId = post.id
|
||||
}
|
||||
|
||||
if (!postId && pageSlug) {
|
||||
const page = await getPageBySlug(pageSlug)
|
||||
if (!page) {
|
||||
return { ok: true }
|
||||
}
|
||||
pageId = page.id
|
||||
}
|
||||
|
||||
const sessionHash = createSessionHashFromEvent(event, body.clientSessionId)
|
||||
|
||||
await recordAnalyticsHeartbeat({
|
||||
@@ -63,6 +75,8 @@ export const handleAnalyticsHeartbeat = async (event) => {
|
||||
path: body.path,
|
||||
postId,
|
||||
postSlug,
|
||||
pageId,
|
||||
pageSlug,
|
||||
durationSeconds: body.durationSeconds,
|
||||
maxScrollRatio: body.maxScrollRatio
|
||||
})
|
||||
|
||||
@@ -2,9 +2,10 @@ import { z } from 'zod'
|
||||
import {
|
||||
isBotUserAgent,
|
||||
isTrackableAnalyticsPath,
|
||||
normalizePageSlugForAnalytics,
|
||||
normalizePostSlugForAnalytics
|
||||
} from '../../lib/analytics.js'
|
||||
import { getPostBySlug } from '../repositories/content-repository.js'
|
||||
import { getPageBySlug, getPostBySlug } from '../repositories/content-repository.js'
|
||||
import {
|
||||
createVisitorHashFromEvent,
|
||||
recordAnalyticsPageview
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
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)
|
||||
})
|
||||
|
||||
@@ -43,7 +45,9 @@ export const handleAnalyticsPageview = async (event) => {
|
||||
}
|
||||
|
||||
const postSlug = normalizePostSlugForAnalytics(body.postSlug)
|
||||
const pageSlug = normalizePageSlugForAnalytics(body.pageSlug)
|
||||
let postId = null
|
||||
let pageId = null
|
||||
|
||||
if (postSlug) {
|
||||
const post = await getPostBySlug(postSlug)
|
||||
@@ -53,14 +57,23 @@ export const handleAnalyticsPageview = async (event) => {
|
||||
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) && !isReadEvent,
|
||||
recordView: (Boolean(postId) || Boolean(pageId)) && !isReadEvent,
|
||||
recordRead: Boolean(postId) && isReadEvent
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user