Skip to content

Commit

Permalink
Merge branch 'develop' into 10th-Kampus-BE-41
Browse files Browse the repository at this point in the history
  • Loading branch information
u-genuine authored Jan 26, 2025
2 parents c57509b + 9a921de commit c6b59c6
Show file tree
Hide file tree
Showing 42 changed files with 1,009 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.board.dao.BoardRepository;
import com.cotato.kampus.domain.board.domain.Board;
import com.cotato.kampus.domain.board.dto.BoardWithFavoriteStatusDto;
import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class BoardReader {
public class BoardFinder {
private final BoardRepository boardRepository;

public List<BoardWithFavoriteStatusDto> readAll(){
public List<BoardWithFavoriteStatusDto> findAll(){
return boardRepository.findAll().stream()
.map(board -> BoardWithFavoriteStatusDto.of(
board.getId(),
Expand All @@ -26,4 +29,9 @@ public List<BoardWithFavoriteStatusDto> readAll(){
))
.toList();
}

public Board findBoard(Long boardId){
return boardRepository.findById(boardId)
.orElseThrow(() -> new AppException(ErrorCode.BOARD_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@RequiredArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class BoardService {

private final BoardReader boardReader;
private final BoardFinder boardFinder;
private final UniversityBoardReader universityBoardReader;
private final BoardValidator boardValidator;
private final BoardFavoriteReader boardFavoriteReader;
Expand All @@ -29,7 +29,7 @@ public List<BoardWithFavoriteStatusDto> getBoardList(){
Set<Long> favoriteBoardIds = boardFavoriteReader.read();

// 전체 게시판 조회
List<BoardWithFavoriteStatusDto> boards = boardReader.readAll();
List<BoardWithFavoriteStatusDto> boards = boardFinder.findAll();

// 즐겨찾기 여부 매핑
return BoardDtoEnhancer.updateFavoriteStatus(boards, favoriteBoardIds);
Expand All @@ -40,7 +40,7 @@ public List<BoardWithFavoriteStatusDto> getFavoriteBoardList() {
Set<Long> favoriteBoardIds = boardFavoriteReader.read();

// 전체 게시판 조회
List<BoardWithFavoriteStatusDto> boards = boardReader.readAll();
List<BoardWithFavoriteStatusDto> boards = boardFinder.findAll();

// 즐겨찾는 게시판만 필터링
return BoardDtoEnhancer.filterFavoriteBoards(boards, favoriteBoardIds);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.cotato.kampus.domain.comment.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.cotato.kampus.domain.comment.application.CommentService;
import com.cotato.kampus.domain.comment.dto.request.CommentCreateRequest;
import com.cotato.kampus.domain.comment.dto.response.CommentCreateResponse;
import com.cotato.kampus.domain.comment.dto.response.CommentDeleteResponse;
import com.cotato.kampus.domain.comment.dto.response.CommentLikeResponse;
import com.cotato.kampus.domain.comment.dto.response.CommentListResponse;
import com.cotato.kampus.domain.comment.dto.response.MyCommentResponse;
import com.cotato.kampus.global.common.dto.DataResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Tag(name = "댓글(Comment) API", description = "댓글 관련 API")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/v1/api")
public class CommentController {

private final CommentService commentService;

@PostMapping("/posts/{postId}/comments")
@Operation(summary = "댓글 작성", description = "특정 게시글에 댓글을 추가합니다. 대댓글일 경우 parentId에 원래 댓글의 id를 넣어주세요. (기본값 = null)")
public ResponseEntity<DataResponse<CommentCreateResponse>> createComment(
@PathVariable Long postId,
@RequestBody CommentCreateRequest request){

return ResponseEntity.ok(DataResponse.from(
CommentCreateResponse.of(
commentService.createComment(
postId,
request.content(),
request.anonymity(),
request.parentId()
)
)
)
);
}

@DeleteMapping("/comments/{commentId}")
@Operation(summary = "댓글 삭제", description = "특정 댓글을 삭제합니다.")
public ResponseEntity<DataResponse<CommentDeleteResponse>> deleteComment(
@PathVariable Long commentId
){

return ResponseEntity.ok(DataResponse.from(
CommentDeleteResponse.of(
commentService.deleteComment(
commentId
)
)
)
);
}

@PostMapping("/comments/{commentId}/like")
@Operation(summary = "댓글 좋아요 추가", description = "특정 댓글에 좋아요를 추가합니다.")
public ResponseEntity<DataResponse<CommentLikeResponse>> toggleLikeForComment(
@PathVariable Long commentId
){

return ResponseEntity.ok(DataResponse.from(
CommentLikeResponse.of(
commentService.likeComment(
commentId
)
)
)
);
}

@GetMapping("/posts/{postId}/comments")
@Operation(summary ="댓글 조회", description = "특정 게시글의 모든 댓글과 대댓글을 조회합니다.")
public ResponseEntity<DataResponse<CommentListResponse>> getPostComments(
@PathVariable Long postId
){

return ResponseEntity.ok(DataResponse.from(
CommentListResponse.from(
commentService.findAllCommentsForPost(
postId
)
)
)
);
}

@GetMapping("/comments/my")
@Operation(summary = "내가 쓴 댓글 조회", description = "현재 사용자가 작성한 댓글을 최신순으로 조회합니다.")
public ResponseEntity<DataResponse<MyCommentResponse>> getMyComments(
@RequestParam(required = false, defaultValue = "0") int page
){
return ResponseEntity.ok(DataResponse.from(
MyCommentResponse.from(
commentService.findUserComments(page)
)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.cotato.kampus.domain.comment.application;

import java.util.Optional;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.comment.dao.CommentRepository;
import com.cotato.kampus.domain.comment.domain.Comment;
import com.cotato.kampus.domain.comment.dto.CommentDto;
import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.domain.common.enums.Anonymity;
import com.cotato.kampus.domain.post.application.PostUpdater;
import com.cotato.kampus.domain.user.application.UserFinder;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class AuthorResolver {

private final CommentRepository commentRepository;
private final ApiUserResolver apiUserResolver;
private final PostUpdater postUpdater;
private final UserFinder userFinder;

public Optional<Long> allocateAnonymousNumber(Long postId, Anonymity anonymity){

// 익명인 경우
if(anonymity == Anonymity.ANONYMOUS){
// 해당 Post에 현재 User의 댓글 작성 여부 확인
Optional<Comment> comment = commentRepository.findFirstByPostIdAndUserIdAndAnonymity(
postId, apiUserResolver.getUserId(), anonymity
);

return comment.map(Comment::getAnonymousNumber)
.or(() -> Optional.ofNullable(postUpdater.increaseNextAnonymousNumber(postId)));
} else {
return Optional.empty();
}
}

public String resolveAuthorName(CommentDto commentDto){

if(commentDto.anonymity() == Anonymity.ANONYMOUS){
return "Anonymous" + commentDto.anonymousNumber();
}

String nickname = userFinder.findById(commentDto.userId()).getNickname();

return nickname;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.cotato.kampus.domain.comment.application;

import java.util.Optional;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.comment.dao.CommentRepository;
import com.cotato.kampus.domain.comment.domain.Comment;
import com.cotato.kampus.domain.comment.enums.CommentStatus;
import com.cotato.kampus.domain.comment.enums.ReportStatus;
import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.domain.common.enums.Anonymity;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentAppender {

private final CommentRepository commentRepository;
private final ApiUserResolver apiUserResolver;

@Transactional
public Long append(Long postId, String content, Anonymity anonymity, Optional<Long> anonymousNumber, Long parentId){

Long userId = apiUserResolver.getUserId();

Long anonymousNumberValue = anonymousNumber.orElse(null);

Comment comment = Comment.builder()
.userId(userId)
.postId(postId)
.content(content)
.likes(0L)
.reportStatus(ReportStatus.NORMAL)
.commentStatus(CommentStatus.NORMAL)
.anonymity(anonymity)
.reports(0L)
.anonymousNumber(anonymousNumberValue)
.parentId(parentId)
.build();

return commentRepository.save(comment).getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.cotato.kampus.domain.comment.application;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.comment.dao.CommentRepository;
import com.cotato.kampus.domain.comment.domain.Comment;
import com.cotato.kampus.domain.comment.enums.CommentStatus;
import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.domain.user.domain.User;
import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentDeleter {

private final CommentRepository commentRepository;
private final ApiUserResolver apiUserResolver;
private final CommentFinder commentFinder;

public Long delete(Long commentId) {

User user = apiUserResolver.getUser();
Comment comment = commentFinder.findComment(commentId);

// 작성자 검증
if(comment.getUserId() != user.getId()){
throw new AppException(ErrorCode.COMMENT_NOT_AUTHOR);
}

// 대댓글이 있으면 삭제된 상태로 업데이트
if(commentRepository.existsByParentId(commentId)){
comment.setCommentStatus(CommentStatus.DELETED_BY_USER);
return commentRepository.save(comment).getId();
}

// 댓글 삭제
commentRepository.delete(comment);
return commentId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.cotato.kampus.domain.comment.application;

import java.util.List;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.comment.dao.CommentRepository;
import com.cotato.kampus.domain.comment.domain.Comment;
import com.cotato.kampus.domain.comment.dto.CommentDto;
import com.cotato.kampus.domain.comment.dto.CommentSummary;
import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentFinder {

private final CommentRepository commentRepository;
private final ApiUserResolver apiUserResolver;

public Comment findComment(Long commentId){
return commentRepository.findById(commentId)
.orElseThrow(() -> new AppException(ErrorCode.COMMENT_NOT_FOUND));
}

public List<CommentDto> findComments(Long postId){
List<Comment> comments = commentRepository.findAllByPostIdOrderByCreatedTimeAsc(postId);
List<CommentDto> commentDtos = comments.stream()
.map(CommentDto::from)
.toList();

return commentDtos;
}

public Slice<CommentSummary> findUserComments(int page){

Long userId = apiUserResolver.getUserId();

PageRequest pageRequest = PageRequest.of(page, 10, Sort.by(Sort.Direction.DESC, "createdTime"));
Slice<Comment> comments = commentRepository.findAllByUserId(userId, pageRequest);

return comments.map(CommentSummary::from);
}
}
Loading

0 comments on commit c6b59c6

Please sign in to comment.