ssulbox/backend/app/routers/comments.py

89 lines
3.7 KiB
Python

# -*- coding: utf-8 -*-
"""댓글 라우터 (2-depth: 댓글+대댓글, 익명, 소프트 삭제)."""
from collections import defaultdict
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import get_session
from ..deps import Pagination, get_current_user, get_current_user_optional, get_pagination
from ..models import Comment, Content, User
from ..schemas import CommentItem, CommentReq, PaginatedResponse, ReplyItem
router = APIRouter(prefix="/api/comment", tags=["Comment"])
@router.post("/content/{cid}")
async def create_comment(cid: str, body: CommentReq, user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)):
if not body.body.strip():
raise HTTPException(400, "내용을 입력해주세요.")
content = await session.get(Content, cid)
if content is None or content.is_deleted:
raise HTTPException(404, "콘텐츠를 찾을 수 없습니다.")
if body.parent_id is not None:
parent = await session.get(Comment, body.parent_id)
if parent is None or parent.content_id != cid:
raise HTTPException(400, "원 댓글을 찾을 수 없습니다.")
if parent.parent_id is not None:
raise HTTPException(400, "대댓글에는 답글을 달 수 없습니다 (최대 2-depth).")
c = Comment(
content_id=cid, user_uuid=user.user_uuid, parent_id=body.parent_id,
nickname=(body.nickname or user.nickname or "익명")[:50], body=body.body.strip()[:300],
)
session.add(c)
await session.commit()
return {"id": c.id}
@router.get("/content/{cid}", response_model=PaginatedResponse[CommentItem])
async def list_comments(cid: str, pg: Pagination = Depends(get_pagination),
user: User | None = Depends(get_current_user_optional),
session: AsyncSession = Depends(get_session)):
me = user.user_uuid if user else None
top_where = [Comment.content_id == cid, Comment.parent_id.is_(None)]
total = (await session.execute(select(func.count(Comment.id)).where(*top_where))).scalar() or 0
tops = (
await session.execute(
select(Comment).where(*top_where).order_by(Comment.created_at.desc())
.offset(pg.offset).limit(pg.page_size)
)
).scalars().all()
parent_ids = [c.id for c in tops]
replies_by_parent: dict[int, list[Comment]] = defaultdict(list)
if parent_ids:
reps = (
await session.execute(
select(Comment).where(Comment.parent_id.in_(parent_ids)).order_by(Comment.created_at.asc())
)
).scalars().all()
for r in reps:
replies_by_parent[r.parent_id].append(r)
def out(c: Comment):
return dict(
id=c.id, body=(None if c.is_deleted else c.body),
nickname=c.nickname, is_mine=(c.user_uuid == me), created_at=c.created_at,
)
items = [
CommentItem(**out(c), replies=[ReplyItem(**out(r)) for r in replies_by_parent.get(c.id, [])])
for c in tops
]
return PaginatedResponse.create(items, total, pg.page, pg.page_size)
@router.delete("/{comment_id}")
async def delete_comment(comment_id: int, user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)):
c = await session.get(Comment, comment_id)
if c is None or c.is_deleted:
raise HTTPException(404, "댓글을 찾을 수 없습니다.")
if c.user_uuid != user.user_uuid:
raise HTTPException(403, "본인 댓글만 삭제할 수 있습니다.")
c.is_deleted = True
await session.commit()
return {"ok": True}