게시물과 페이지 공개 상태 확장 v1.5.4
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { listPosts } from '../repositories/content-repository'
|
||||
import { getMemberSession } from '../utils/member-auth'
|
||||
|
||||
/**
|
||||
* 공개 게시물 목록 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Array} 게시물 목록
|
||||
*/
|
||||
export default defineEventHandler(() => listPosts())
|
||||
export default defineEventHandler((event) => listPosts({
|
||||
includeMembers: Boolean(getMemberSession(event))
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getPostBySlug } from '../../repositories/content-repository'
|
||||
import { getMemberSession } from '../../utils/member-auth'
|
||||
|
||||
/**
|
||||
* 공개 게시물 상세 API
|
||||
@@ -7,7 +8,9 @@ import { getPostBySlug } from '../../repositories/content-repository'
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const post = await getPostBySlug(slug)
|
||||
const post = await getPostBySlug(slug, {
|
||||
includeMembers: Boolean(getMemberSession(event))
|
||||
})
|
||||
|
||||
if (!post) {
|
||||
throw createError({
|
||||
|
||||
@@ -35,7 +35,7 @@ const mapPostRow = (row) => ({
|
||||
canonicalUrl: row.canonical_url || '',
|
||||
noindex: Boolean(row.noindex),
|
||||
ogImage: row.og_image || null,
|
||||
status: row.status === 'private' ? 'draft' : row.status,
|
||||
status: row.status,
|
||||
publishedAt: row.published_at ? row.published_at.toISOString() : null,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString(),
|
||||
@@ -63,6 +63,7 @@ const mapPageRow = (row) => ({
|
||||
slug: row.slug,
|
||||
content: row.content,
|
||||
renderMode: row.render_mode || 'markdown',
|
||||
status: row.status || 'published',
|
||||
featuredImage: row.featured_image,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString()
|
||||
@@ -210,9 +211,10 @@ const syncPostTags = async (sql, postId, tags) => {
|
||||
|
||||
/**
|
||||
* 공개 게시물 목록 조회
|
||||
* @param {{ includeMembers?: boolean }} [options] - 회원 전용 글 포함 여부
|
||||
* @returns {Promise<Array>} 게시물 목록
|
||||
*/
|
||||
export const listPosts = async () => {
|
||||
export const listPosts = async ({ includeMembers = false } = {}) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
@@ -232,9 +234,13 @@ export const listPosts = async () => {
|
||||
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'
|
||||
WHERE (
|
||||
posts.status = 'published'
|
||||
OR (${includeMembers} = true AND posts.status = 'members')
|
||||
)
|
||||
AND (
|
||||
posts.published_at IS NULL
|
||||
posts.status = 'members'
|
||||
OR posts.published_at IS NULL
|
||||
OR posts.published_at <= now()
|
||||
)
|
||||
GROUP BY posts.id
|
||||
@@ -439,9 +445,10 @@ export const deleteAdminPost = async (id) => {
|
||||
/**
|
||||
* 공개 게시물 상세 조회
|
||||
* @param {string} slug - 게시물 슬러그
|
||||
* @param {{ includeMembers?: boolean }} [options] - 회원 전용 글 포함 여부
|
||||
* @returns {Promise<Object | null>} 게시물 상세
|
||||
*/
|
||||
export const getPostBySlug = async (slug) => {
|
||||
export const getPostBySlug = async (slug, { includeMembers = false } = {}) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
@@ -462,9 +469,13 @@ export const getPostBySlug = async (slug) => {
|
||||
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'
|
||||
AND (
|
||||
posts.published_at IS NULL
|
||||
posts.status = 'published'
|
||||
OR (${includeMembers} = true AND posts.status = 'members')
|
||||
)
|
||||
AND (
|
||||
posts.status = 'members'
|
||||
OR posts.published_at IS NULL
|
||||
OR posts.published_at <= now()
|
||||
)
|
||||
GROUP BY posts.id
|
||||
@@ -488,6 +499,7 @@ export const listPages = async () => {
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM pages
|
||||
WHERE status = 'published'
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
@@ -498,7 +510,21 @@ export const listPages = async () => {
|
||||
* 관리자 고정 페이지 목록 조회
|
||||
* @returns {Promise<Array>} 관리자 고정 페이지 목록
|
||||
*/
|
||||
export const listAdminPages = async () => listPages()
|
||||
export const listAdminPages = async () => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return getSamplePages()
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM pages
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
return rows.map(mapPageRow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 고정 페이지 상세 조회
|
||||
@@ -540,14 +566,16 @@ export const createAdminPage = async (input) => {
|
||||
slug,
|
||||
content,
|
||||
render_mode,
|
||||
featured_image
|
||||
featured_image,
|
||||
status
|
||||
)
|
||||
VALUES (
|
||||
${input.title},
|
||||
${input.slug},
|
||||
${input.content},
|
||||
${input.renderMode},
|
||||
${input.featuredImage}
|
||||
${input.featuredImage},
|
||||
${input.status}
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -576,6 +604,7 @@ export const updateAdminPage = async (id, input) => {
|
||||
content = ${input.content},
|
||||
render_mode = ${input.renderMode},
|
||||
featured_image = ${input.featuredImage},
|
||||
status = ${input.status},
|
||||
updated_at = now()
|
||||
WHERE id = ${id}
|
||||
RETURNING *
|
||||
@@ -621,6 +650,7 @@ export const getPageBySlug = async (slug) => {
|
||||
SELECT *
|
||||
FROM pages
|
||||
WHERE slug = ${slug}
|
||||
AND status = 'published'
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
import { normalizeMarkdownContent } from '../../lib/markdown-content-normalizer.js'
|
||||
import { pageStatusSchema } from './content-schema.js'
|
||||
|
||||
export const adminPageInputSchema = z.object({
|
||||
title: z.string().trim().min(1),
|
||||
slug: z.string().trim().min(1).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
||||
renderMode: z.enum(['markdown', 'html_document']).default('html_document'),
|
||||
status: pageStatusSchema.default('published'),
|
||||
content: z.string().default(''),
|
||||
featuredImage: z.string().trim().nullable().default(null)
|
||||
}).transform((input) => ({
|
||||
|
||||
@@ -18,7 +18,7 @@ export const adminPostInputSchema = z.object({
|
||||
canonicalUrl: z.string().trim().url().or(z.literal('')).default(''),
|
||||
noindex: z.boolean().default(false),
|
||||
ogImage: z.string().trim().nullable().default(null),
|
||||
status: z.preprocess((val) => (val === 'private' ? 'draft' : val), postStatusSchema).default('draft'),
|
||||
status: postStatusSchema.default('draft'),
|
||||
publishedAt: z.string().datetime().nullable().default(null),
|
||||
tags: z.array(z.string().trim().min(1)).default([])
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const postStatusSchema = z.enum(['published', 'draft'])
|
||||
export const postStatusSchema = z.enum(['published', 'draft', 'members', 'private'])
|
||||
export const pageStatusSchema = z.enum(['published', 'draft', 'private'])
|
||||
|
||||
export const postSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
@@ -29,6 +30,7 @@ export const pageSchema = z.object({
|
||||
slug: z.string().min(1),
|
||||
content: z.string(),
|
||||
renderMode: z.enum(['markdown', 'html_document']).default('markdown'),
|
||||
status: pageStatusSchema.default('published'),
|
||||
featuredImage: z.string().nullable().default(null),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string()
|
||||
|
||||
Reference in New Issue
Block a user