관리자 글 링크 참조 확인 추가

This commit is contained in:
2026-07-10 15:50:13 +09:00
parent e48f886956
commit d708381e09
9 changed files with 420 additions and 6 deletions

View File

@@ -109,6 +109,36 @@ const mapAdminPostSummaryRow = (row) => ({
tags: row.tags || []
})
/**
* 정규식 특수 문자를 이스케이프한다.
* @param {string} value - 원본 문자열
* @returns {string} 이스케이프된 문자열
*/
const escapeRegExp = (value) => String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
/**
* 게시물 공개 링크 참조 정규식을 생성한다.
* @param {string} slug - 게시물 슬러그
* @returns {RegExp} 게시물 링크 정규식
*/
const createPostLinkReferencePattern = (slug) => {
return new RegExp(`(?:^|[\\s("'\\[])(?:https?:\\/\\/[^/\\s)"'<>]+)?\\/post\\/${escapeRegExp(slug)}(?=$|[\\s)"'<>?#/])`, 'gi')
}
/**
* 본문에서 특정 게시물 링크 참조 개수를 계산한다.
* @param {string} content - 게시물 본문
* @param {string} slug - 참조 대상 슬러그
* @returns {number} 참조 개수
*/
const countPostLinkReferences = (content, slug) => {
if (!slug) {
return 0
}
return [...String(content || '').matchAll(createPostLinkReferencePattern(slug))].length
}
/**
* 관리자 글 목록 페이지 표시 개수를 정규화한다.
* @param {unknown} value - 요청 표시 개수
@@ -816,6 +846,76 @@ export const listMediaReferencePosts = async () => {
}))
}
/**
* 게시물을 링크하고 있는 관리자 게시물 목록을 조회한다.
* @param {string} id - 참조 대상 게시물 ID
* @returns {Promise<{ target: { id: string, title: string, slug: string }, posts: Array<{ id: string, title: string, slug: string, status: string, publishedAt: string | null, updatedAt: string, referenceCount: number }> } | null>} 참조 게시물 목록
*/
export const listAdminPostReferences = async (id) => {
const sql = getPostgresClient()
const target = await getAdminPostById(id)
if (!target) {
return null
}
const mapReferenceRow = (post) => ({
id: post.id,
title: toAdminPostFormTitle(post.title),
slug: post.slug,
status: post.status,
publishedAt: post.published_at
? post.published_at.toISOString()
: post.publishedAt || null,
updatedAt: post.updated_at
? post.updated_at.toISOString()
: post.updatedAt || '',
referenceCount: countPostLinkReferences(post.content, target.slug)
})
if (!sql) {
const posts = getSamplePosts()
.filter((post) => post.id !== target.id)
.map(mapReferenceRow)
.filter((post) => post.referenceCount > 0)
return {
target: {
id: target.id,
title: target.title,
slug: target.slug
},
posts
}
}
const rows = await sql`
SELECT
id,
title,
slug,
status,
published_at,
updated_at,
content
FROM posts
WHERE id <> ${target.id}
AND content ILIKE ${`%/post/${target.slug}%`}
ORDER BY updated_at DESC
`
return {
target: {
id: target.id,
title: target.title,
slug: target.slug
},
posts: rows
.map(mapReferenceRow)
.filter((post) => post.referenceCount > 0)
}
}
/**
* 관리자 게시물 상세 조회
* @param {string} id - 게시물 ID

View File

@@ -0,0 +1,24 @@
import { createError, getRouterParam } from 'h3'
import { requireAdminSession } from '../../../../../utils/admin-auth'
import { listAdminPostReferences } from '../../../../../repositories/content-repository'
/**
* 관리자 게시물 참조 목록 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<{ target: { id: string, title: string, slug: string }, posts: Array }>} 참조 목록
*/
export default defineEventHandler(async (event) => {
requireAdminSession(event)
const id = getRouterParam(event, 'id')
const references = await listAdminPostReferences(id)
if (!references) {
throw createError({
statusCode: 404,
message: '게시물을 찾을 수 없습니다.'
})
}
return references
})