태그를 관리용/일반용으로 분리하고 관리자 드래그 정렬을 추가.

댓글/회원/관리자 인증·프로필 흐름 보완과 관련 마이그레이션 및 문서를 함께 반영해 운영 동선을 안정화.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 18:34:23 +09:00
parent b18aca4dcc
commit cdc16c72b2
35 changed files with 1721 additions and 138 deletions

View File

@@ -59,7 +59,8 @@ const mapTagRow = (row) => ({
slug: row.slug,
description: row.description,
sortOrder: row.sort_order,
color: row.color
color: row.color,
tagType: row.tag_type || 'managed'
})
/**
@@ -532,22 +533,45 @@ export const getPageBySlug = async (slug) => {
* 공개 태그 목록 조회
* @returns {Promise<Array>} 태그 목록
*/
export const listTags = async () => {
export const listTags = async ({ tagType } = {}) => {
const sql = getPostgresClient()
if (!sql) {
return getSampleTags()
const sampleTags = getSampleTags().map((tag) => ({
...tag,
tagType: 'managed'
}))
if (!tagType) {
return sampleTags
}
return sampleTags.filter((tag) => tag.tagType === tagType)
}
const rows = await sql`
SELECT *
FROM tags
ORDER BY sort_order ASC, name ASC
`
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
`
return rows.map(mapTagRow)
}
/**
* 관리자 태그 목록 조회
* @returns {Promise<Array>} 관리자 태그 목록
*/
export const listAdminTags = async () => listTags()
const SEARCH_TAG_LIMIT = 12
const SEARCH_POST_LIMIT = 12
const SEARCH_POST_CANDIDATE_LIMIT = 48
@@ -823,8 +847,8 @@ export const createAdminTag = async (input) => {
}
const rows = await sql`
INSERT INTO tags (name, slug, description, sort_order, color)
VALUES (${input.name}, ${input.slug}, ${input.description}, ${input.sortOrder}, ${input.color})
INSERT INTO tags (name, slug, description, sort_order, color, tag_type)
VALUES (${input.name}, ${input.slug}, ${input.description}, ${input.sortOrder}, ${input.color}, ${input.tagType})
RETURNING *
`
@@ -852,6 +876,7 @@ export const updateAdminTag = async (id, input) => {
description = ${input.description},
sort_order = ${input.sortOrder},
color = ${input.color},
tag_type = ${input.tagType},
updated_at = now()
WHERE id = ${id}
RETURNING *
@@ -860,6 +885,35 @@ export const updateAdminTag = async (id, input) => {
return rows[0] ? mapTagRow(rows[0]) : null
}
/**
* 관리자 관리용 태그 순서를 일괄 갱신
* @param {Array<string>} tagIds - 정렬된 태그 ID 목록
* @returns {Promise<Array>} 갱신된 태그 목록
*/
export const reorderManagedTags = async (tagIds) => {
const sql = getPostgresClient()
if (!sql) {
throw new Error('DATABASE_REQUIRED')
}
await sql.begin(async (transaction) => {
for (let index = 0; index < tagIds.length; index += 1) {
const tagId = tagIds[index]
await transaction`
UPDATE tags
SET
sort_order = ${(index + 1) * 10},
updated_at = now()
WHERE id = ${tagId}
AND tag_type = 'managed'
`
}
})
return listTags({ tagType: 'managed' })
}
/**
* 관리자 태그 삭제
* @param {string} id - 태그 ID