Files
sori.studio/server/routes/admin/api/settings/backup-summary.get.js
zenn 2e5a973d11 관리자 설정 사이트 용량 카드를 간결한 통계형 UI로 정리
백업 현황을 사이트 용량으로 재정의하고 파일 집계를 이미지·영상·오디오·첨부 파일·보내기로 명확히 분리해 합계 불일치를 해소했다. 설정 화면의 통계 카드 스타일을 통일해 가독성을 높였다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 16:29:03 +09:00

150 lines
4.7 KiB
JavaScript

import { requireAdminSession } from '../../../../utils/admin-auth'
import { getBackupStorageSummary } from '../../../../utils/backup-storage-summary'
import { getPostgresClient } from '../../../../repositories/postgres-client'
/**
* 빈 콘텐츠 집계를 만든다.
* @returns {Object} 콘텐츠 집계
*/
const createEmptyContentSummary = () => ({
posts: {
total: 0,
published: 0,
draft: 0,
private: 0,
members: 0,
scheduled: 0
},
pages: {
total: 0,
published: 0,
draft: 0,
private: 0,
listed: 0,
featured: 0
},
tags: {
total: 0,
managed: 0,
general: 0,
used: 0
},
comments: {
total: 0,
published: 0,
pending: 0,
blocked: 0
},
members: {
total: 0,
owner: 0,
admin: 0,
member: 0
}
})
/**
* 숫자 값을 안전하게 정수로 변환한다.
* @param {unknown} value - 원본 값
* @returns {number} 정수 값
*/
const toCount = (value) => Number(value || 0)
/**
* 백업 점검용 콘텐츠 수량 집계를 조회한다.
* @returns {Promise<Object>} 콘텐츠 수량 집계
*/
const getBackupContentSummary = async () => {
const sql = getPostgresClient()
if (!sql) {
return createEmptyContentSummary()
}
const rows = await sql`
SELECT
(SELECT COUNT(*)::int FROM posts) AS "postsTotal",
(SELECT COUNT(*)::int FROM posts WHERE status = 'published') AS "postsPublished",
(SELECT COUNT(*)::int FROM posts WHERE status = 'draft') AS "postsDraft",
(SELECT COUNT(*)::int FROM posts WHERE status = 'private') AS "postsPrivate",
(SELECT COUNT(*)::int FROM posts WHERE status = 'members') AS "postsMembers",
(SELECT COUNT(*)::int FROM posts WHERE status IN ('published', 'members') AND published_at > now()) AS "postsScheduled",
(SELECT COUNT(*)::int FROM pages) AS "pagesTotal",
(SELECT COUNT(*)::int FROM pages WHERE status = 'published') AS "pagesPublished",
(SELECT COUNT(*)::int FROM pages WHERE status = 'draft') AS "pagesDraft",
(SELECT COUNT(*)::int FROM pages WHERE status = 'private') AS "pagesPrivate",
(SELECT COUNT(*)::int FROM pages WHERE is_listed = true) AS "pagesListed",
(SELECT COUNT(*)::int FROM pages WHERE is_featured = true) AS "pagesFeatured",
(SELECT COUNT(*)::int FROM tags) AS "tagsTotal",
(SELECT COUNT(*)::int FROM tags WHERE tag_type = 'managed') AS "tagsManaged",
(SELECT COUNT(*)::int FROM tags WHERE tag_type = 'general') AS "tagsGeneral",
(SELECT COUNT(DISTINCT tag_id)::int FROM post_tags) AS "tagsUsed",
(SELECT COUNT(*)::int FROM comments) AS "commentsTotal",
(SELECT COUNT(*)::int FROM comments WHERE status = 'published') AS "commentsPublished",
(SELECT COUNT(*)::int FROM comments WHERE status = 'pending') AS "commentsPending",
(SELECT COUNT(*)::int FROM comments WHERE status = 'blocked') AS "commentsBlocked",
(SELECT COUNT(*)::int FROM users) AS "membersTotal",
(SELECT COUNT(*)::int FROM users WHERE user_role = 'owner') AS "membersOwner",
(SELECT COUNT(*)::int FROM users WHERE user_role = 'admin') AS "membersAdmin",
(SELECT COUNT(*)::int FROM users WHERE user_role = 'member') AS "membersMember"
`
const row = rows[0] || {}
return {
posts: {
total: toCount(row.postsTotal),
published: toCount(row.postsPublished),
draft: toCount(row.postsDraft),
private: toCount(row.postsPrivate),
members: toCount(row.postsMembers),
scheduled: toCount(row.postsScheduled)
},
pages: {
total: toCount(row.pagesTotal),
published: toCount(row.pagesPublished),
draft: toCount(row.pagesDraft),
private: toCount(row.pagesPrivate),
listed: toCount(row.pagesListed),
featured: toCount(row.pagesFeatured)
},
tags: {
total: toCount(row.tagsTotal),
managed: toCount(row.tagsManaged),
general: toCount(row.tagsGeneral),
used: toCount(row.tagsUsed)
},
comments: {
total: toCount(row.commentsTotal),
published: toCount(row.commentsPublished),
pending: toCount(row.commentsPending),
blocked: toCount(row.commentsBlocked)
},
members: {
total: toCount(row.membersTotal),
owner: toCount(row.membersOwner),
admin: toCount(row.membersAdmin),
member: toCount(row.membersMember)
}
}
}
/**
* 관리자 사이트 용량 조회 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<Object>} 백업 점검용 현황
*/
export default defineEventHandler(async (event) => {
requireAdminSession(event)
const [content, storage] = await Promise.all([
getBackupContentSummary(),
getBackupStorageSummary()
])
return {
generatedAt: new Date().toISOString(),
content,
storage
}
})