import { createError } from 'h3' import { getPostgresClient } from './postgres-client' /** * @typedef {Object} PostComment * @property {string} id - 댓글 ID * @property {string} postId - 게시물 ID * @property {string | null} parentId - 부모 댓글 ID * @property {string} body - 댓글 내용 * @property {string} status - 댓글 상태 * @property {string} createdAt - 생성 시각 * @property {string} updatedAt - 수정 시각 * @property {{ id: string, username: string }} user - 작성자 정보 */ /** * DB 클라이언트 조회 (선택) * @returns {ReturnType | null} postgres sql 클라이언트 */ const getSql = () => getPostgresClient() /** * 게시물 ID 조회 * @param {ReturnType} sql - postgres 클라이언트 * @param {string} slug - 게시물 슬러그 * @returns {Promise} 게시물 ID */ const findPublishedPostIdBySlug = async (sql, slug) => { const rows = await sql` SELECT id FROM posts WHERE slug = ${slug} AND status = 'published' AND ( published_at IS NULL OR published_at <= now() ) LIMIT 1 ` return rows?.[0]?.id || null } /** * 게시물 댓글 조회 * @param {string} slug - 게시물 슬러그 * @returns {Promise>} 댓글 목록 */ export const listPostCommentsBySlug = async (slug) => { const sql = getSql() if (!sql) { return [] } const postId = await findPublishedPostIdBySlug(sql, slug) if (!postId) { throw createError({ statusCode: 404, statusMessage: '게시물을 찾을 수 없습니다' }) } const rows = await sql` SELECT comments.id, comments.post_id AS "postId", comments.parent_id AS "parentId", comments.body, comments.status, comments.created_at AS "createdAt", comments.updated_at AS "updatedAt", users.id AS "userId", users.username AS "username" FROM comments INNER JOIN users ON users.id = comments.user_id WHERE comments.post_id = ${postId} AND comments.status = 'published' ORDER BY comments.created_at ASC ` return rows.map((row) => ({ id: row.id, postId: row.postId, parentId: row.parentId || null, body: row.body, status: row.status, createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), user: { id: row.userId, username: row.username } })) } /** * 댓글 생성 * @param {{ slug: string, userId: string, body: string, parentId?: string | null }} input - 댓글 입력값 * @returns {Promise} 생성된 댓글 */ export const createComment = async (input) => { const sql = getSql() if (!sql) { throw createError({ statusCode: 500, message: '데이터베이스 설정이 필요합니다.' }) } const postId = await findPublishedPostIdBySlug(sql, input.slug) if (!postId) { throw createError({ statusCode: 404, statusMessage: '게시물을 찾을 수 없습니다' }) } let parentId = input.parentId || null if (parentId) { const parentRows = await sql` SELECT id, post_id AS "postId", parent_id AS "parentId", status FROM comments WHERE id = ${parentId} LIMIT 1 ` const parent = parentRows?.[0] if (!parent || parent.postId !== postId || parent.status !== 'published') { throw createError({ statusCode: 400, message: '유효하지 않은 부모 댓글입니다.' }) } if (parent.parentId) { throw createError({ statusCode: 400, message: '대댓글에는 추가 답글을 달 수 없습니다.' }) } } const rows = await sql` INSERT INTO comments (post_id, user_id, parent_id, body, status) VALUES (${postId}, ${input.userId}, ${parentId}, ${input.body}, 'published') RETURNING id, post_id AS "postId", parent_id AS "parentId", body, status, created_at AS "createdAt", updated_at AS "updatedAt" ` const created = rows?.[0] if (!created) { throw createError({ statusCode: 500, message: '댓글 생성에 실패했습니다.' }) } const userRows = await sql` SELECT id, username FROM users WHERE id = ${input.userId} LIMIT 1 ` const user = userRows?.[0] if (!user) { throw createError({ statusCode: 401, message: '회원 정보를 찾을 수 없습니다.' }) } return { id: created.id, postId: created.postId, parentId: created.parentId || null, body: created.body, status: created.status, createdAt: created.createdAt.toISOString(), updatedAt: created.updatedAt.toISOString(), user: { id: user.id, username: user.username } } }