@@ -18,30 +18,75 @@ def _verify_password(password: str, hashed: str) -> bool:
1818 return bcrypt .checkpw (password .encode (), hashed .encode ())
1919
2020
21+ def _build_response (comment : Comment , replies : list [Comment ]) -> CommentResponse :
22+ return CommentResponse (
23+ id = comment .id ,
24+ post_slug = comment .post_slug ,
25+ nickname = comment .nickname ,
26+ content = comment .content ,
27+ created_at = comment .created_at ,
28+ parent_id = comment .parent_id ,
29+ replies = [
30+ CommentResponse (
31+ id = r .id ,
32+ post_slug = r .post_slug ,
33+ nickname = r .nickname ,
34+ content = r .content ,
35+ created_at = r .created_at ,
36+ parent_id = r .parent_id ,
37+ replies = [],
38+ )
39+ for r in replies
40+ ],
41+ )
42+
43+
2144@router .get ("/{slug:path}/comments" , response_model = list [CommentResponse ])
2245def get_comments (slug : str , db : Session = Depends (get_db )):
23- """특정 포스트의 댓글 목록 조회"""
24- return (
46+ """특정 포스트의 댓글 목록 조회 (대댓글 포함 중첩 구조) """
47+ all_comments = (
2548 db .query (Comment )
2649 .filter (Comment .post_slug == slug )
2750 .order_by (Comment .created_at .asc ())
2851 .all ()
2952 )
3053
54+ replies_map : dict [int , list [Comment ]] = {}
55+ top_level : list [Comment ] = []
56+
57+ for c in all_comments :
58+ if c .parent_id is None :
59+ top_level .append (c )
60+ replies_map [c .id ] = []
61+ else :
62+ replies_map .setdefault (c .parent_id , []).append (c )
63+
64+ return [_build_response (c , replies_map .get (c .id , [])) for c in top_level ]
65+
3166
3267@router .post ("/{slug:path}/comments" , response_model = CommentResponse , status_code = status .HTTP_201_CREATED )
3368def create_comment (slug : str , body : CommentCreate , db : Session = Depends (get_db )):
34- """댓글 작성"""
69+ """댓글/대댓글 작성"""
70+ if body .parent_id is not None :
71+ parent = db .query (Comment ).filter (
72+ Comment .id == body .parent_id ,
73+ Comment .post_slug == slug ,
74+ Comment .parent_id == None , # noqa: E711 대댓글의 대댓글 방지
75+ ).first ()
76+ if not parent :
77+ raise HTTPException (status_code = 404 , detail = "원댓글을 찾을 수 없습니다." )
78+
3579 comment = Comment (
3680 post_slug = slug ,
3781 nickname = body .nickname ,
3882 password_hash = _hash_password (body .password ),
3983 content = body .content ,
84+ parent_id = body .parent_id ,
4085 )
4186 db .add (comment )
4287 db .commit ()
4388 db .refresh (comment )
44- return comment
89+ return _build_response ( comment , [])
4590
4691
4792@router .delete ("/{slug:path}/comments/{comment_id}" , status_code = status .HTTP_204_NO_CONTENT )
@@ -51,7 +96,7 @@ def delete_comment(
5196 body : CommentDelete ,
5297 db : Session = Depends (get_db ),
5398):
54- """비밀번호 확인 후 댓글 삭제"""
99+ """비밀번호 확인 후 댓글 삭제 (대댓글도 함께 삭제) """
55100 comment = (
56101 db .query (Comment )
57102 .filter (Comment .id == comment_id , Comment .post_slug == slug )
@@ -63,5 +108,9 @@ def delete_comment(
63108 if not _verify_password (body .password , comment .password_hash ):
64109 raise HTTPException (status_code = 403 , detail = "비밀번호가 올바르지 않습니다." )
65110
111+ # 원댓글이면 대댓글도 함께 삭제
112+ if comment .parent_id is None :
113+ db .query (Comment ).filter (Comment .parent_id == comment_id ).delete ()
114+
66115 db .delete (comment )
67116 db .commit ()
0 commit comments