89 lines
3.7 KiB
Python
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}
|