사이트 설정 관리 추가

This commit is contained in:
2026-05-02 16:37:11 +09:00
parent d5666fdcc3
commit 27cf05aba6
18 changed files with 431 additions and 25 deletions

View File

@@ -0,0 +1,7 @@
import { getSiteSettings } from '../repositories/content-repository'
/**
* 공개 사이트 설정 API
* @returns {Promise<Object>} 사이트 설정
*/
export default defineEventHandler(() => getSiteSettings())

View File

@@ -5,6 +5,7 @@ import {
getSamplePosts,
getSampleTags
} from '../utils/sample-content'
import { getDefaultSiteSettings } from '../utils/site-settings'
import { getPostgresClient } from './postgres-client'
/**
@@ -55,6 +56,20 @@ const mapTagRow = (row) => ({
color: row.color
})
/**
* 사이트 설정 행을 API 응답 구조로 변환
* @param {Object} row - 사이트 설정 행
* @returns {Object} 사이트 설정 응답
*/
const mapSiteSettingsRow = (row) => ({
title: row.title,
description: row.description,
siteUrl: row.site_url,
logoText: row.logo_text,
copyrightText: row.copyright_text,
updatedAt: row.updated_at.toISOString()
})
/**
* 태그 슬러그 목록 정규화
* @param {Array<string>} tags - 태그 슬러그 목록
@@ -488,6 +503,72 @@ export const listTags = async () => {
return rows.map(mapTagRow)
}
/**
* 사이트 설정 조회
* @returns {Promise<Object>} 사이트 설정
*/
export const getSiteSettings = async () => {
const sql = getPostgresClient()
if (!sql) {
return getDefaultSiteSettings()
}
const rows = await sql`
SELECT *
FROM site_settings
WHERE id = 1
LIMIT 1
`
return rows[0] ? mapSiteSettingsRow(rows[0]) : getDefaultSiteSettings()
}
/**
* 관리자 사이트 설정 수정
* @param {Object} input - 사이트 설정 입력값
* @returns {Promise<Object>} 수정된 사이트 설정
*/
export const updateSiteSettings = async (input) => {
const sql = getPostgresClient()
if (!sql) {
throw new Error('DATABASE_REQUIRED')
}
const rows = await sql`
INSERT INTO site_settings (
id,
title,
description,
site_url,
logo_text,
copyright_text,
updated_at
)
VALUES (
1,
${input.title},
${input.description},
${input.siteUrl},
${input.logoText},
${input.copyrightText},
now()
)
ON CONFLICT (id) DO UPDATE
SET
title = EXCLUDED.title,
description = EXCLUDED.description,
site_url = EXCLUDED.site_url,
logo_text = EXCLUDED.logo_text,
copyright_text = EXCLUDED.copyright_text,
updated_at = now()
RETURNING *
`
return mapSiteSettingsRow(rows[0])
}
/**
* 관리자 태그 상세 조회
* @param {string} id - 태그 ID

View File

@@ -0,0 +1,13 @@
import { requireAdminSession } from '../../../utils/admin-auth'
import { getSiteSettings } from '../../../repositories/content-repository'
/**
* 관리자 사이트 설정 조회 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<Object>} 사이트 설정
*/
export default defineEventHandler((event) => {
requireAdminSession(event)
return getSiteSettings()
})

View File

@@ -0,0 +1,24 @@
import { createError, readBody } from 'h3'
import { requireAdminSession } from '../../../utils/admin-auth'
import { parseAdminSiteSettingsInput } from '../../../utils/admin-site-settings-input'
import { updateSiteSettings } from '../../../repositories/content-repository'
/**
* 관리자 사이트 설정 수정 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<Object>} 수정된 사이트 설정
*/
export default defineEventHandler(async (event) => {
requireAdminSession(event)
const parsedBody = parseAdminSiteSettingsInput(await readBody(event))
if (!parsedBody.success) {
throw createError({
statusCode: 400,
message: '사이트 설정 입력 형식이 올바르지 않습니다.'
})
}
return updateSiteSettings(parsedBody.data)
})

View File

@@ -0,0 +1,16 @@
import { z } from 'zod'
export const adminSiteSettingsInputSchema = z.object({
title: z.string().trim().min(1),
description: z.string().trim().default(''),
siteUrl: z.string().trim().url(),
logoText: z.string().trim().min(1).max(8),
copyrightText: z.string().trim().min(1)
})
/**
* 관리자 사이트 설정 입력값 정리
* @param {unknown} body - 요청 본문
* @returns {import('zod').SafeParseReturnType<unknown, Object>} 검증 결과
*/
export const parseAdminSiteSettingsInput = (body) => adminSiteSettingsInputSchema.safeParse(body)

View File

@@ -0,0 +1,17 @@
/**
* 기본 사이트 설정 반환
* @returns {Object} 기본 사이트 설정
*/
export const getDefaultSiteSettings = () => {
const config = useRuntimeConfig()
const title = config.public.siteTitle || 'sori.studio'
return {
title,
description: 'sori.studio 개인 블로그',
siteUrl: config.public.siteUrl || 'https://sori.studio',
logoText: '井',
copyrightText: `©${new Date().getFullYear()} ${title}`,
updatedAt: null
}
}