관리자 통계 대시보드 개선 및 v1.9.6 반영
통계 추이 차트 공통화, 유입 스택 막대, 집계 시작일 고정, traffic API 오류 수정, deploy 문서 정리를 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@ const mapPostRow = (row) => ({
|
||||
showFeaturedImage: Boolean(row.show_featured_image),
|
||||
isFeatured: Boolean(row.is_featured),
|
||||
commentCount: Number(row.comment_count || 0),
|
||||
totalViews: Number(row.total_views || 0),
|
||||
seoTitle: row.seo_title || '',
|
||||
seoDescription: row.seo_description || '',
|
||||
canonicalUrl: row.canonical_url || '',
|
||||
@@ -80,6 +81,7 @@ const mapPageRow = (row) => ({
|
||||
renderMode: row.render_mode || 'markdown',
|
||||
status: row.status || 'published',
|
||||
featuredImage: row.featured_image,
|
||||
totalViews: Number(row.total_views || 0),
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString()
|
||||
})
|
||||
@@ -298,6 +300,11 @@ export const listPosts = async ({ includeMembership = false } = {}) => {
|
||||
WHERE comments.post_id = posts.id
|
||||
AND comments.status = 'published'
|
||||
) AS comment_count,
|
||||
(
|
||||
SELECT COALESCE(SUM(post_analytics_daily.views), 0)::int
|
||||
FROM post_analytics_daily
|
||||
WHERE post_analytics_daily.post_id = posts.id
|
||||
) AS total_views,
|
||||
COALESCE(array_agg(tags.slug ORDER BY post_tags.sort_order ASC, post_tags.created_at ASC, tags.name ASC) FILTER (WHERE tags.slug IS NOT NULL), '{}') AS tags
|
||||
FROM posts
|
||||
LEFT JOIN post_tags ON post_tags.post_id = posts.id
|
||||
@@ -338,6 +345,11 @@ export const listAdminPosts = async () => {
|
||||
WHERE comments.post_id = posts.id
|
||||
AND comments.status = 'published'
|
||||
) AS comment_count,
|
||||
(
|
||||
SELECT COALESCE(SUM(post_analytics_daily.views), 0)::int
|
||||
FROM post_analytics_daily
|
||||
WHERE post_analytics_daily.post_id = posts.id
|
||||
) AS total_views,
|
||||
COALESCE(array_agg(tags.slug ORDER BY post_tags.sort_order ASC, post_tags.created_at ASC, tags.name ASC) FILTER (WHERE tags.slug IS NOT NULL), '{}') AS tags
|
||||
FROM posts
|
||||
LEFT JOIN post_tags ON post_tags.post_id = posts.id
|
||||
@@ -595,7 +607,13 @@ export const listAdminPages = async () => {
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
SELECT
|
||||
pages.*,
|
||||
(
|
||||
SELECT COALESCE(SUM(page_analytics_daily.views), 0)::int
|
||||
FROM page_analytics_daily
|
||||
WHERE page_analytics_daily.page_id = pages.id
|
||||
) AS total_views
|
||||
FROM pages
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
26
server/routes/admin/api/analytics/pages/[id].get.js
Normal file
26
server/routes/admin/api/analytics/pages/[id].get.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createError, getQuery, getRouterParam } from 'h3'
|
||||
import { requireAdminSession } from '../../../../../utils/admin-auth.js'
|
||||
import { getPageAnalyticsDetail } from '../../../../../repositories/analytics-repository.js'
|
||||
|
||||
/**
|
||||
* 관리자 페이지별 통계 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<Object>} 페이지별 통계
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAdminSession(event)
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '페이지 ID가 필요합니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const days = Number(query.days) || 30
|
||||
const period = String(query.period || 'daily')
|
||||
|
||||
return getPageAnalyticsDetail({ pageId: id, days, period })
|
||||
})
|
||||
26
server/routes/admin/api/analytics/posts/[id].get.js
Normal file
26
server/routes/admin/api/analytics/posts/[id].get.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createError, getQuery, getRouterParam } from 'h3'
|
||||
import { requireAdminSession } from '../../../../../utils/admin-auth.js'
|
||||
import { getPostAnalyticsDetail } from '../../../../../repositories/analytics-repository.js'
|
||||
|
||||
/**
|
||||
* 관리자 게시물별 통계 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<Object>} 게시물별 통계
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAdminSession(event)
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '게시물 ID가 필요합니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
const days = Number(query.days) || 30
|
||||
const period = String(query.period || 'daily')
|
||||
|
||||
return getPostAnalyticsDetail({ postId: id, days, period })
|
||||
})
|
||||
19
server/routes/admin/api/analytics/range-detail.get.js
Normal file
19
server/routes/admin/api/analytics/range-detail.get.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { requireAdminSession } from '../../../../utils/admin-auth.js'
|
||||
import { getAnalyticsRangeDetail } from '../../../../repositories/analytics-repository.js'
|
||||
|
||||
/**
|
||||
* 관리자 날짜별 통계 상세 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<Object>} 날짜 상세
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAdminSession(event)
|
||||
|
||||
const query = getQuery(event)
|
||||
|
||||
return getAnalyticsRangeDetail({
|
||||
startDay: String(query.startDay || ''),
|
||||
endDay: String(query.endDay || ''),
|
||||
limit: Number(query.limit) || 5
|
||||
})
|
||||
})
|
||||
@@ -11,6 +11,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const query = getQuery(event)
|
||||
const days = Number(query.days) || 30
|
||||
const period = String(query.period || 'weekly')
|
||||
|
||||
return getAnalyticsSummary({ days })
|
||||
return getAnalyticsSummary({ days, period })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user