태그 관리 화면을 메인/일반 전환 중심으로 단순화하고 삭제 동선을 재정리.
글쓰기 Post URL 슬러그는 한글 입력 시 발음 기반 영문 소문자로 자동 생성되도록 개선. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -130,8 +130,8 @@ const syncPostTags = async (sql, postId, tags) => {
|
||||
|
||||
for (const slug of tagSlugs) {
|
||||
const tagRows = await sql`
|
||||
INSERT INTO tags (name, slug)
|
||||
VALUES (${getTagNameFromSlug(slug)}, ${slug})
|
||||
INSERT INTO tags (name, slug, tag_type, sort_order)
|
||||
VALUES (${getTagNameFromSlug(slug)}, ${slug}, 'general', 0)
|
||||
ON CONFLICT (slug) DO UPDATE
|
||||
SET updated_at = now()
|
||||
RETURNING id
|
||||
@@ -533,44 +533,54 @@ export const getPageBySlug = async (slug) => {
|
||||
* 공개 태그 목록 조회
|
||||
* @returns {Promise<Array>} 태그 목록
|
||||
*/
|
||||
export const listTags = async ({ tagType } = {}) => {
|
||||
export const listTags = async ({ tagType, searchQuery = '', limit } = {}) => {
|
||||
const sql = getPostgresClient()
|
||||
const trimmedSearchQuery = String(searchQuery || '').trim().toLowerCase()
|
||||
const resolvedLimit = Number.isInteger(limit) && limit > 0 ? limit : null
|
||||
|
||||
if (!sql) {
|
||||
const sampleTags = getSampleTags().map((tag) => ({
|
||||
...tag,
|
||||
tagType: 'managed'
|
||||
}))
|
||||
if (!tagType) {
|
||||
return sampleTags
|
||||
let filteredTags = sampleTags
|
||||
if (tagType) {
|
||||
filteredTags = filteredTags.filter((tag) => tag.tagType === tagType)
|
||||
}
|
||||
return sampleTags.filter((tag) => tag.tagType === tagType)
|
||||
if (trimmedSearchQuery) {
|
||||
filteredTags = filteredTags.filter((tag) =>
|
||||
tag.name.toLowerCase().includes(trimmedSearchQuery) ||
|
||||
tag.slug.toLowerCase().includes(trimmedSearchQuery)
|
||||
)
|
||||
}
|
||||
return resolvedLimit ? filteredTags.slice(0, resolvedLimit) : filteredTags
|
||||
}
|
||||
|
||||
const rows = tagType
|
||||
? await sql`
|
||||
SELECT *
|
||||
FROM tags
|
||||
WHERE tag_type = ${tagType}
|
||||
ORDER BY sort_order ASC, name ASC
|
||||
`
|
||||
: await sql`
|
||||
SELECT *
|
||||
FROM tags
|
||||
ORDER BY
|
||||
CASE tag_type WHEN 'managed' THEN 0 ELSE 1 END ASC,
|
||||
sort_order ASC,
|
||||
name ASC
|
||||
`
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM tags
|
||||
WHERE (${tagType || null}::text IS NULL OR tag_type = ${tagType || null})
|
||||
AND (
|
||||
${trimmedSearchQuery || null}::text IS NULL
|
||||
OR strpos(lower(name), ${trimmedSearchQuery || ''}) > 0
|
||||
OR strpos(lower(slug), ${trimmedSearchQuery || ''}) > 0
|
||||
)
|
||||
ORDER BY
|
||||
CASE tag_type WHEN 'managed' THEN 0 ELSE 1 END ASC,
|
||||
sort_order ASC,
|
||||
name ASC
|
||||
LIMIT ${resolvedLimit || 1000}
|
||||
`
|
||||
|
||||
return rows.map(mapTagRow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 태그 목록 조회
|
||||
* @param {Object} options - 조회 옵션
|
||||
* @returns {Promise<Array>} 관리자 태그 목록
|
||||
*/
|
||||
export const listAdminTags = async () => listTags()
|
||||
export const listAdminTags = async (options = {}) => listTags(options)
|
||||
|
||||
const SEARCH_TAG_LIMIT = 12
|
||||
const SEARCH_POST_LIMIT = 12
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getQuery } from 'h3'
|
||||
import { requireAdminSession } from '../../../utils/admin-auth'
|
||||
import { listAdminTags } from '../../../repositories/content-repository'
|
||||
|
||||
@@ -8,6 +9,17 @@ import { listAdminTags } from '../../../repositories/content-repository'
|
||||
*/
|
||||
export default defineEventHandler((event) => {
|
||||
requireAdminSession(event)
|
||||
const query = getQuery(event)
|
||||
const tagType = query.tagType === 'managed' || query.tagType === 'general'
|
||||
? query.tagType
|
||||
: undefined
|
||||
const searchQuery = typeof query.q === 'string' ? query.q : ''
|
||||
const parsedLimit = Number.parseInt(String(query.limit || ''), 10)
|
||||
const limit = Number.isNaN(parsedLimit) ? undefined : parsedLimit
|
||||
|
||||
return listAdminTags()
|
||||
return listAdminTags({
|
||||
tagType,
|
||||
searchQuery,
|
||||
limit
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user