""" Comment API Router 영상 댓글 관련 엔드포인트를 제공합니다. 엔드포인트 목록: - POST /comment/video/{video_id}: 댓글/대댓글 작성 (로그인 필수) - GET /comment/video/{video_id}: 댓글 목록 조회 (비로그인 허용) - DELETE /comment/{comment_id}: 본인 댓글 소프트 삭제 (로그인 필수) """ from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from app.comment.schemas.comment_schema import ( CommentCreateRequest, CommentCreateResponse, CommentItem, DeleteCommentResponse, ) from app.comment.services.comment import create_comment, delete_comment, list_comments from app.database.session import get_session from app.dependencies.pagination import PaginationParams, get_pagination_params from app.user.dependencies.auth import get_current_user, get_current_user_optional from app.user.models import User from app.utils.logger import get_logger from app.utils.pagination import PaginatedResponse logger = get_logger("comment") router = APIRouter(prefix="/comment", tags=["Comment"]) @router.post( "/video/{video_id}", summary="댓글/대댓글 작성", description=""" ## 개요 영상에 댓글 또는 대댓글을 작성합니다. 로그인 필수. ## 경로 파라미터 - **video_id**: 댓글을 달 영상의 ID ## 요청 본문 - **content**: 댓글 본문 (1~100자) - **parent_id**: 대댓글일 때만 부모 댓글 id (생략 시 최상위 댓글) ## 참고 - 작성자 정보는 응답에 포함되지 않습니다 (익명 정책). - 대댓글에 또 대댓글을 다는 것은 불가합니다 (최대 2-depth). """, response_model=CommentCreateResponse, responses={ 200: {"description": "댓글 작성 성공"}, 400: {"description": "잘못된 parent_id (2-depth 초과, 다른 영상의 댓글 등)"}, 401: {"description": "인증 실패"}, 404: {"description": "영상을 찾을 수 없음"}, }, ) async def post_comment( video_id: int, body: CommentCreateRequest, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> CommentCreateResponse: logger.info( f"[post_comment] START - video_id: {video_id}, user: {current_user.user_uuid}, " f"parent_id: {body.parent_id}" ) comment = await create_comment( session=session, video_id=video_id, user_uuid=current_user.user_uuid, nickname=body.nickname, content=body.content, parent_id=body.parent_id, ) logger.info(f"[post_comment] SUCCESS - comment_id: {comment.id}") return CommentCreateResponse( id=comment.id, nickname=comment.nickname or "익명", parent_id=comment.parent_id, content=comment.content, created_at=comment.created_at, ) @router.get( "/video/{video_id}", summary="댓글 목록 조회", description=""" ## 개요 영상의 댓글 목록을 페이지네이션하여 반환합니다. 비로그인도 접근 가능. ## 경로 파라미터 - **video_id**: 댓글을 조회할 영상의 ID ## 쿼리 파라미터 - **page**: 페이지 번호 (기본값: 1) - **page_size**: 페이지당 댓글 수 (기본값: 10, 최대: 100) ## 참고 - 최상위 댓글만 페이지네이션됩니다. 각 댓글의 대댓글은 전부 포함됩니다. - 작성자 정보는 노출되지 않으며, is_mine으로 본인 댓글 여부만 확인 가능합니다. - 삭제된 댓글은 content=null로 노출됩니다 (대댓글이 있는 경우). """, response_model=PaginatedResponse[CommentItem], responses={ 200: {"description": "댓글 목록 조회 성공"}, 500: {"description": "조회 실패"}, }, ) async def get_comments( video_id: int, current_user: User | None = Depends(get_current_user_optional), session: AsyncSession = Depends(get_session), pagination: PaginationParams = Depends(get_pagination_params), ) -> PaginatedResponse[CommentItem]: logger.info( f"[get_comments] START - video_id: {video_id}, " f"page: {pagination.page}, page_size: {pagination.page_size}" ) current_user_uuid = current_user.user_uuid if current_user else None result = await list_comments( session=session, video_id=video_id, page=pagination.page, page_size=pagination.page_size, current_user_uuid=current_user_uuid, ) logger.info(f"[get_comments] SUCCESS - total: {result.total}, items: {len(result.items)}") return result @router.delete( "/{comment_id}", summary="댓글 소프트 삭제", description=""" ## 개요 본인이 작성한 댓글을 소프트 삭제합니다. 로그인 필수. ## 경로 파라미터 - **comment_id**: 삭제할 댓글의 ID ## 참고 - 본인 댓글만 삭제 가능합니다. - 소프트 삭제 방식으로 DB에 데이터는 유지됩니다. - 부모 댓글 삭제 시 대댓글은 유지되며, 목록 조회 시 content=null로 표시됩니다. """, response_model=DeleteCommentResponse, responses={ 200: {"description": "삭제 성공"}, 401: {"description": "인증 실패"}, 403: {"description": "삭제 권한 없음"}, 404: {"description": "댓글을 찾을 수 없음"}, }, ) async def remove_comment( comment_id: int, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> DeleteCommentResponse: logger.info( f"[remove_comment] START - comment_id: {comment_id}, user: {current_user.user_uuid}" ) await delete_comment( session=session, comment_id=comment_id, current_user_uuid=current_user.user_uuid, ) logger.info(f"[remove_comment] SUCCESS - comment_id: {comment_id}") return DeleteCommentResponse( success=True, comment_id=comment_id, message="댓글이 삭제되었습니다.", )