태그 슬러그와 코드 라벨 개선

This commit is contained in:
2026-06-26 16:48:56 +09:00
parent 5247eb293c
commit 066c58f311
11 changed files with 93 additions and 17 deletions

View File

@@ -174,16 +174,24 @@ const normalizeTagSlugs = (tags = []) => {
const out = []
for (const tag of tags) {
const trimmed = String(tag).trim().normalize('NFC')
if (!trimmed) {
const normalized = String(tag)
.trim()
.normalize('NFC')
.toLowerCase()
.replace(/[\\/]+/g, '-')
.replace(/\s+/g, '-')
.replace(/[^a-z0-9가-힣-]+/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
if (!normalized) {
continue
}
const dedupeKey = trimmed.toLowerCase()
const dedupeKey = normalized.toLowerCase()
if (seen.has(dedupeKey)) {
continue
}
seen.add(dedupeKey)
out.push(trimmed)
out.push(normalized)
}
return out
@@ -210,14 +218,37 @@ const getTagNameFromSlug = (slug) => {
.join(' ')
}
/**
* 새 태그 표시 이름을 정리한다.
* @param {string} value - 원본 표시 이름
* @returns {string} 표시 이름
*/
const normalizeTagDisplayName = (value) => String(value || '')
.normalize('NFC')
.trim()
.replace(/\s*\/\s*/g, '/')
.replace(/\s+/g, ' ')
/**
* 태그 슬러그에 대응하는 표시 이름을 반환한다.
* @param {string} slug - 태그 슬러그
* @param {Object} tagNames - 태그 표시 이름 맵
* @returns {string} 태그명
*/
const resolveTagName = (slug, tagNames = {}) => {
const inputName = normalizeTagDisplayName(tagNames[slug])
return inputName || getTagNameFromSlug(slug)
}
/**
* 게시물 태그 연결 저장
* @param {Function} sql - PostgreSQL 트랜잭션 클라이언트
* @param {string} postId - 게시물 ID
* @param {Array<string>} tags - 태그 슬러그 목록
* @param {Object} tagNames - 태그 표시 이름 맵
* @returns {Promise<void>} 저장 결과
*/
const syncPostTags = async (sql, postId, tags) => {
const syncPostTags = async (sql, postId, tags, tagNames = {}) => {
const tagSlugs = normalizeTagSlugs(tags)
await sql`
@@ -228,7 +259,7 @@ const syncPostTags = async (sql, postId, tags) => {
for (const [index, slug] of tagSlugs.entries()) {
const tagRows = await sql`
INSERT INTO tags (name, slug, tag_type, sort_order)
VALUES (${getTagNameFromSlug(slug)}, ${slug}, 'general', 0)
VALUES (${resolveTagName(slug, tagNames)}, ${slug}, 'general', 0)
ON CONFLICT (slug) DO UPDATE
SET updated_at = now()
RETURNING id
@@ -400,7 +431,7 @@ export const createAdminPost = async (input, authorId) => {
RETURNING *
`
await syncPostTags(transaction, insertedRows[0].id, input.tags)
await syncPostTags(transaction, insertedRows[0].id, input.tags, input.tagNames)
return insertedRows
})
@@ -452,7 +483,7 @@ export const updateAdminPost = async (id, input, editorId) => {
return []
}
await syncPostTags(transaction, id, input.tags)
await syncPostTags(transaction, id, input.tags, input.tagNames)
return updatedRows
})

View File

@@ -26,7 +26,8 @@ export const adminPostInputSchema = z.object({
ogImage: z.string().trim().nullable().default(null),
status: postStatusSchema.default('draft'),
publishedAt: z.string().datetime().nullable().default(null),
tags: z.array(z.string().trim().min(1)).default([])
tags: z.array(z.string().trim().min(1)).default([]),
tagNames: z.record(z.string()).default({})
})
/**