관리자 글 목록 고정 기능 추가

This commit is contained in:
2026-07-03 15:19:41 +09:00
parent 55efae810f
commit 288e73c0dc
11 changed files with 160 additions and 16 deletions

View File

@@ -59,6 +59,7 @@ const mapPostRow = (row) => ({
featuredImageThumbnail: getExistingPostThumbnailUrl(row.featured_image),
showFeaturedImage: Boolean(row.show_featured_image),
isFeatured: Boolean(row.is_featured),
adminPinned: Boolean(row.admin_pinned),
commentCount: Number(row.comment_count || 0),
totalViews: Number(row.total_views || 0),
seoTitle: row.seo_title || '',
@@ -98,6 +99,7 @@ const mapAdminPostSummaryRow = (row) => ({
featuredImageThumbnail: getExistingPostThumbnailUrl(row.featured_image),
showFeaturedImage: Boolean(row.show_featured_image),
isFeatured: Boolean(row.is_featured),
adminPinned: Boolean(row.admin_pinned),
commentCount: Number(row.comment_count || 0),
totalViews: Number(row.total_views || 0),
status: row.status,
@@ -574,6 +576,10 @@ export const listAdminPosts = async (options = {}) => {
return matchesStatus && matchesTag && matchesFeatured && matchesSearch
})
samplePosts.sort((left, right) => {
if (Boolean(left.adminPinned) !== Boolean(right.adminPinned)) {
return left.adminPinned ? -1 : 1
}
const leftTime = new Date(left.sortAt || 0).getTime()
const rightTime = new Date(right.sortAt || 0).getTime()
@@ -597,7 +603,8 @@ export const listAdminPosts = async (options = {}) => {
},
counts: {
total: getSamplePosts().length,
featured: getSamplePosts().filter((post) => post.isFeatured).length
featured: getSamplePosts().filter((post) => post.isFeatured).length,
pinned: getSamplePosts().filter((post) => post.adminPinned).length
}
}
}
@@ -612,6 +619,7 @@ export const listAdminPosts = async (options = {}) => {
posts.featured_image,
posts.show_featured_image,
posts.is_featured,
posts.admin_pinned,
posts.status,
posts.published_at,
posts.created_at,
@@ -680,11 +688,13 @@ export const listAdminPosts = async (options = {}) => {
posts.featured_image,
posts.show_featured_image,
posts.is_featured,
posts.admin_pinned,
posts.status,
posts.published_at,
posts.created_at,
posts.updated_at
ORDER BY
posts.admin_pinned DESC,
CASE WHEN ${normalizedSortOrder} = 'oldest' THEN COALESCE(posts.published_at, posts.updated_at) END ASC,
CASE WHEN ${normalizedSortOrder} <> 'oldest' THEN COALESCE(posts.published_at, posts.updated_at) END DESC,
posts.id DESC
@@ -699,7 +709,8 @@ export const listAdminPosts = async (options = {}) => {
const countsRows = await sql`
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE posts.is_featured = true)::int AS featured
COUNT(*) FILTER (WHERE posts.is_featured = true)::int AS featured,
COUNT(*) FILTER (WHERE posts.admin_pinned = true)::int AS pinned
FROM posts
`
@@ -759,7 +770,8 @@ export const listAdminPosts = async (options = {}) => {
},
counts: {
total: Number(countsRows[0]?.total || 0),
featured: Number(countsRows[0]?.featured || 0)
featured: Number(countsRows[0]?.featured || 0),
pinned: Number(countsRows[0]?.pinned || 0)
}
}
}
@@ -944,6 +956,31 @@ export const updateAdminPostFeatured = async (id, isFeatured) => {
return rows[0] ? getAdminPostById(rows[0].id) : null
}
/**
* 관리자 게시물 고정 상태 수정
* @param {string} id - 게시물 ID
* @param {boolean} adminPinned - 관리자 목록 상단 고정 여부
* @returns {Promise<Object | null>} 수정된 게시물
*/
export const updateAdminPostPinned = async (id, adminPinned) => {
const sql = getPostgresClient()
if (!sql) {
throw new Error('DATABASE_REQUIRED')
}
const rows = await sql`
UPDATE posts
SET
admin_pinned = ${Boolean(adminPinned)},
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 { updateAdminPostPinned } from '../../../../../repositories/content-repository'
const pinnedInputSchema = z.object({
adminPinned: 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 = pinnedInputSchema.safeParse(await readBody(event))
if (!id) {
throw createError({
statusCode: 400,
message: '게시물 ID가 필요합니다.'
})
}
if (!parsedBody.success) {
throw createError({
statusCode: 400,
message: '고정 상태 입력 형식이 올바르지 않습니다.'
})
}
const post = await updateAdminPostPinned(id, parsedBody.data.adminPinned)
if (!post) {
throw createError({
statusCode: 404,
message: '게시물을 찾을 수 없습니다.'
})
}
return post
})