PostgreSQL 데이터 계층 추가
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { getSamplePages } from '../utils/sample-content'
|
||||
import { listPages } from '../repositories/content-repository'
|
||||
|
||||
/**
|
||||
* 공개 고정 페이지 목록 API
|
||||
* @returns {Array} 고정 페이지 목록
|
||||
*/
|
||||
export default defineEventHandler(() => getSamplePages())
|
||||
export default defineEventHandler(() => listPages())
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getSamplePageBySlug } from '../../utils/sample-content'
|
||||
import { getPageBySlug } from '../../repositories/content-repository'
|
||||
|
||||
/**
|
||||
* 공개 고정 페이지 상세 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Object} 고정 페이지 상세
|
||||
*/
|
||||
export default defineEventHandler((event) => {
|
||||
export default defineEventHandler(async (event) => {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const page = getSamplePageBySlug(slug)
|
||||
const page = await getPageBySlug(slug)
|
||||
|
||||
if (!page) {
|
||||
throw createError({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSamplePosts } from '../utils/sample-content'
|
||||
import { listPosts } from '../repositories/content-repository'
|
||||
|
||||
/**
|
||||
* 공개 게시물 목록 API
|
||||
* @returns {Array} 게시물 목록
|
||||
*/
|
||||
export default defineEventHandler(() => getSamplePosts())
|
||||
export default defineEventHandler(() => listPosts())
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getSamplePostBySlug } from '../../utils/sample-content'
|
||||
import { getPostBySlug } from '../../repositories/content-repository'
|
||||
|
||||
/**
|
||||
* 공개 게시물 상세 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Object} 게시물 상세
|
||||
*/
|
||||
export default defineEventHandler((event) => {
|
||||
export default defineEventHandler(async (event) => {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const post = getSamplePostBySlug(slug)
|
||||
const post = await getPostBySlug(slug)
|
||||
|
||||
if (!post) {
|
||||
throw createError({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSampleTags } from '../utils/sample-content'
|
||||
import { listTags } from '../repositories/content-repository'
|
||||
|
||||
/**
|
||||
* 공개 태그 목록 API
|
||||
* @returns {Array} 태그 목록
|
||||
*/
|
||||
export default defineEventHandler(() => getSampleTags())
|
||||
export default defineEventHandler(() => listTags())
|
||||
|
||||
170
server/repositories/content-repository.js
Normal file
170
server/repositories/content-repository.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
getSamplePageBySlug,
|
||||
getSamplePages,
|
||||
getSamplePostBySlug,
|
||||
getSamplePosts,
|
||||
getSampleTags
|
||||
} from '../utils/sample-content'
|
||||
import { getPostgresClient } from './postgres-client'
|
||||
|
||||
/**
|
||||
* 게시물 행을 API 응답 구조로 변환
|
||||
* @param {Object} row - 게시물 행
|
||||
* @returns {Object} 게시물 응답
|
||||
*/
|
||||
const mapPostRow = (row) => ({
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
slug: row.slug,
|
||||
content: row.content,
|
||||
excerpt: row.excerpt,
|
||||
featuredImage: row.featured_image,
|
||||
status: row.status,
|
||||
publishedAt: row.published_at ? row.published_at.toISOString() : null,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString(),
|
||||
tags: row.tags || []
|
||||
})
|
||||
|
||||
/**
|
||||
* 고정 페이지 행을 API 응답 구조로 변환
|
||||
* @param {Object} row - 고정 페이지 행
|
||||
* @returns {Object} 고정 페이지 응답
|
||||
*/
|
||||
const mapPageRow = (row) => ({
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
slug: row.slug,
|
||||
content: row.content,
|
||||
featuredImage: row.featured_image,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString()
|
||||
})
|
||||
|
||||
/**
|
||||
* 태그 행을 API 응답 구조로 변환
|
||||
* @param {Object} row - 태그 행
|
||||
* @returns {Object} 태그 응답
|
||||
*/
|
||||
const mapTagRow = (row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
slug: row.slug,
|
||||
description: row.description
|
||||
})
|
||||
|
||||
/**
|
||||
* 공개 게시물 목록 조회
|
||||
* @returns {Promise<Array>} 게시물 목록
|
||||
*/
|
||||
export const listPosts = async () => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSamplePosts()
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
posts.*,
|
||||
COALESCE(array_agg(tags.slug) FILTER (WHERE tags.slug IS NOT NULL), '{}') AS tags
|
||||
FROM posts
|
||||
LEFT JOIN post_tags ON post_tags.post_id = posts.id
|
||||
LEFT JOIN tags ON tags.id = post_tags.tag_id
|
||||
WHERE posts.status = 'published'
|
||||
GROUP BY posts.id
|
||||
ORDER BY posts.published_at DESC NULLS LAST, posts.created_at DESC
|
||||
`
|
||||
|
||||
return rows.map(mapPostRow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 공개 게시물 상세 조회
|
||||
* @param {string} slug - 게시물 슬러그
|
||||
* @returns {Promise<Object | null>} 게시물 상세
|
||||
*/
|
||||
export const getPostBySlug = async (slug) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSamplePostBySlug(slug)
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
posts.*,
|
||||
COALESCE(array_agg(tags.slug) FILTER (WHERE tags.slug IS NOT NULL), '{}') AS tags
|
||||
FROM posts
|
||||
LEFT JOIN post_tags ON post_tags.post_id = posts.id
|
||||
LEFT JOIN tags ON tags.id = post_tags.tag_id
|
||||
WHERE posts.slug = ${slug}
|
||||
AND posts.status = 'published'
|
||||
GROUP BY posts.id
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
return rows[0] ? mapPostRow(rows[0]) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 공개 고정 페이지 목록 조회
|
||||
* @returns {Promise<Array>} 고정 페이지 목록
|
||||
*/
|
||||
export const listPages = async () => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSamplePages()
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM pages
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
return rows.map(mapPageRow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 공개 고정 페이지 상세 조회
|
||||
* @param {string} slug - 페이지 슬러그
|
||||
* @returns {Promise<Object | null>} 고정 페이지 상세
|
||||
*/
|
||||
export const getPageBySlug = async (slug) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSamplePageBySlug(slug)
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM pages
|
||||
WHERE slug = ${slug}
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
return rows[0] ? mapPageRow(rows[0]) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 공개 태그 목록 조회
|
||||
* @returns {Promise<Array>} 태그 목록
|
||||
*/
|
||||
export const listTags = async () => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSampleTags()
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM tags
|
||||
ORDER BY name ASC
|
||||
`
|
||||
|
||||
return rows.map(mapTagRow)
|
||||
}
|
||||
25
server/repositories/postgres-client.js
Normal file
25
server/repositories/postgres-client.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import postgres from 'postgres'
|
||||
|
||||
let client = null
|
||||
|
||||
/**
|
||||
* PostgreSQL 클라이언트 조회
|
||||
* @returns {ReturnType<typeof postgres> | null} PostgreSQL 클라이언트
|
||||
*/
|
||||
export const getPostgresClient = () => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
if (!config.databaseUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!client) {
|
||||
client = postgres(config.databaseUrl, {
|
||||
max: 5,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10
|
||||
})
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
Reference in New Issue
Block a user