관리자 글 목록 API 응답 경량화

This commit is contained in:
2026-07-03 10:55:11 +09:00
parent 3f80bbb4ea
commit 94782d5873
12 changed files with 158 additions and 42 deletions

View File

@@ -83,6 +83,30 @@ const mapAdminPostRow = (row) => ({
title: toAdminPostFormTitle(row.title)
})
/**
* 관리자 목록 API용 게시물 요약 행 변환
* @param {Object} row - 게시물 행
* @returns {Object} 게시물 요약 응답
*/
const mapAdminPostSummaryRow = (row) => ({
id: row.id,
title: toAdminPostFormTitle(row.title),
slug: row.slug,
authorId: row.author_id || null,
excerpt: row.excerpt,
featuredImage: row.featured_image,
featuredImageThumbnail: getExistingPostThumbnailUrl(row.featured_image),
showFeaturedImage: Boolean(row.show_featured_image),
isFeatured: Boolean(row.is_featured),
commentCount: Number(row.comment_count || 0),
totalViews: Number(row.total_views || 0),
status: row.status,
publishedAt: row.published_at ? row.published_at.toISOString() : null,
createdAt: row.created_at.toISOString(),
updatedAt: row.updated_at.toISOString(),
tags: row.tags || []
})
/**
* 고정 페이지 행을 API 응답 구조로 변환
* @param {Object} row - 고정 페이지 행
@@ -438,19 +462,33 @@ export const listPosts = async ({
}
/**
* 관리자 게시물 목록 조회
* @returns {Promise<Array>} 관리자 게시물 목록
* 관리자 게시물 목록 요약 조회
* @returns {Promise<Array>} 관리자 게시물 목록 요약
*/
export const listAdminPosts = async () => {
const sql = getPostgresClient()
if (!sql) {
return getSamplePosts()
return getSamplePosts().map((post) => {
const { content, ...summary } = post
return summary
})
}
const rows = await sql`
SELECT
posts.*,
posts.id,
posts.title,
posts.slug,
posts.author_id,
posts.excerpt,
posts.featured_image,
posts.show_featured_image,
posts.is_featured,
posts.status,
posts.published_at,
posts.created_at,
posts.updated_at,
(
SELECT COUNT(*)::int
FROM comments
@@ -466,11 +504,23 @@ export const listAdminPosts = async () => {
FROM posts
LEFT JOIN post_tags ON post_tags.post_id = posts.id
LEFT JOIN tags ON tags.id = post_tags.tag_id
GROUP BY posts.id
GROUP BY
posts.id,
posts.title,
posts.slug,
posts.author_id,
posts.excerpt,
posts.featured_image,
posts.show_featured_image,
posts.is_featured,
posts.status,
posts.published_at,
posts.created_at,
posts.updated_at
ORDER BY COALESCE(posts.published_at, posts.updated_at) DESC
`
return rows.map(mapAdminPostRow)
return rows.map(mapAdminPostSummaryRow)
}
/**
@@ -628,6 +678,31 @@ export const updateAdminPost = async (id, input, editorId) => {
return rows[0] ? getAdminPostById(rows[0].id) : null
}
/**
* 관리자 게시물 추천 상태 수정
* @param {string} id - 게시물 ID
* @param {boolean} isFeatured - 추천 여부
* @returns {Promise<Object | null>} 수정된 게시물
*/
export const updateAdminPostFeatured = async (id, isFeatured) => {
const sql = getPostgresClient()
if (!sql) {
throw new Error('DATABASE_REQUIRED')
}
const rows = await sql`
UPDATE posts
SET
is_featured = ${Boolean(isFeatured)},
updated_at = now()
WHERE id = ${id}
RETURNING id
`
return rows[0] ? getAdminPostById(rows[0].id) : null
}
/**
* 관리자 게시물 삭제
* @param {string} id - 게시물 ID

View File

@@ -0,0 +1,45 @@
import { createError, getRouterParam, readBody } from 'h3'
import { z } from 'zod'
import { requireAdminSession } from '../../../../../utils/admin-auth'
import { updateAdminPostFeatured } from '../../../../../repositories/content-repository'
const featuredInputSchema = z.object({
isFeatured: z.boolean()
})
/**
* 관리자 게시물 추천 상태 변경 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<Object>} 수정된 게시물
*/
export default defineEventHandler(async (event) => {
requireAdminSession(event)
const id = String(getRouterParam(event, 'id') || '').trim()
const parsedBody = featuredInputSchema.safeParse(await readBody(event))
if (!id) {
throw createError({
statusCode: 400,
message: '게시물 ID가 필요합니다.'
})
}
if (!parsedBody.success) {
throw createError({
statusCode: 400,
message: '추천 상태 입력 형식이 올바르지 않습니다.'
})
}
const post = await updateAdminPostFeatured(id, parsedBody.data.isFeatured)
if (!post) {
throw createError({
statusCode: 404,
message: '게시물을 찾을 수 없습니다.'
})
}
return post
})