diff --git a/src/main/java/balancetalk/comment/application/CommentService.java b/src/main/java/balancetalk/comment/application/CommentService.java index 01a75e07a..ba7e99ca4 100644 --- a/src/main/java/balancetalk/comment/application/CommentService.java +++ b/src/main/java/balancetalk/comment/application/CommentService.java @@ -116,27 +116,12 @@ public Page findAllComments(Long talkPickId, Pageable pag Page comments = commentRepository.findAllByTalkPickIdAndParentIsNull(talkPickId, pageable); - return comments.map(comment -> { //TODO : Duplicates 메서드 분리 - int likesCount = likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT); - boolean myLike = isCommentMyLiked(comment.getId(), guestOrApiMember); - Member member = comment.getMember(); - VoteOption option = member.getVoteOnTalkPick(talkPick) - .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; - - if (member.getProfileImgId() == null) { - return LatestCommentResponse.fromEntity(comment, option, null, likesCount, myLike); - } - - String imgUrl = fileRepository.findById(member.getProfileImgId()) - .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)) - .getImgUrl(); - - return LatestCommentResponse.fromEntity(comment, option, imgUrl, likesCount, myLike); - }); + return convertToLatestCommentPagesResponse(comments, talkPick, guestOrApiMember); } @Transactional(readOnly = true) - public List findAllReplies(Long parentId, Long talkPickId, GuestOrApiMember guestOrApiMember) { + public List findAllReplies(Long parentId, Long talkPickId, + GuestOrApiMember guestOrApiMember) { // 부모 댓글이 존재하는지 확인 validateCommentId(parentId); @@ -149,102 +134,107 @@ public List findAllReplies(Long parentId, Long talkPickId // 해당 부모 댓글의 답글 조회 List replies = commentRepository.findAllRepliesByParentIdOrderByMemberAndCreatedAt(parentId, memberId); - return replies.stream().map(reply -> { - int likesCount = likeRepository.countByResourceIdAndLikeType(reply.getId(), LikeType.COMMENT); - boolean myLike = isCommentMyLiked(reply.getId(), guestOrApiMember); - Member member = reply.getMember(); - VoteOption option = member.getVoteOnTalkPick(talkPick) - .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; - if (member.getProfileImgId() == null) { - return LatestCommentResponse.fromEntity(reply, option, null, likesCount, myLike); - } + return convertToLatestCommentResponse(replies, talkPick, guestOrApiMember); + } - String imgUrl = fileRepository.findById(member.getProfileImgId()) - .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)) - .getImgUrl(); + // Page 처리 + private Page convertToLatestCommentPagesResponse(Page comments, TalkPick talkPick, + GuestOrApiMember guestOrApiMember) { + return comments.map(comment -> mapToLatestCommentResponse(comment, talkPick, guestOrApiMember)); + } - return LatestCommentResponse.fromEntity(reply, option, imgUrl, likesCount, myLike);}) + // List 처리 + private List convertToLatestCommentResponse(List comments, TalkPick talkPick, + GuestOrApiMember guestOrApiMember) { + return comments.stream() + .map(comment -> mapToLatestCommentResponse(comment, talkPick, guestOrApiMember)) .toList(); } + // 공통 변환 로직 + private LatestCommentResponse mapToLatestCommentResponse(Comment comment, TalkPick talkPick, + GuestOrApiMember guestOrApiMember) { + int likesCount = likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT); + boolean myLike = isCommentMyLiked(comment.getId(), guestOrApiMember); + Member member = comment.getMember(); + VoteOption option = member.getVoteOnTalkPick(talkPick) + .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; - @Transactional(readOnly = true) + String imgUrl = (member.getProfileImgId() != null) ? fileRepository.findById(member.getProfileImgId()) + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)) + .getImgUrl() : null; + + return LatestCommentResponse.fromEntity(comment, option, imgUrl, likesCount, myLike); + } + + @Transactional public Page findAllBestComments(Long talkPickId, Pageable pageable, GuestOrApiMember guestOrApiMember) { validateTalkPickId(talkPickId); TalkPick talkPick = talkPickRepository.findById(talkPickId) .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_TALK_PICK)); - List allComments = commentRepository.findByTalkPickIdAndParentIsNullOrderByLikesCountDescCreatedAtAsc(talkPickId, - LikeType.COMMENT); - List bestComments = new ArrayList<>(); - List otherComments = new ArrayList<>(); + List allComments = commentRepository.findByTalkPickIdAndParentIsNullOrderByLikesCountDescCreatedAtAsc( + talkPickId, LikeType.COMMENT); - // 최대 좋아요 수 구하기 + // 최대 좋아요 수 계산 int maxLikes = allComments.stream() .mapToInt(comment -> likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT)) .max() .orElse(0); - // 좋아요 10개 이상인 댓글이 있는 경우 - if (maxLikes >= MIN_COUNT_FOR_BEST_COMMENT) { - for (Comment comment : allComments) { - boolean myLike = isCommentMyLiked(comment.getId(), guestOrApiMember); - int likeCount = likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT); - Member member = comment.getMember(); - VoteOption option = member.getVoteOnTalkPick(talkPick) - .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; - comment.setIsBest(likeCount >= MIN_COUNT_FOR_BEST_COMMENT); - BestCommentResponse response = validateImgUrl(member, comment, option, likeCount, myLike); - - if (comment.getIsBest()) { - bestComments.add(response); - } else { - otherComments.add(response); - } - } - } else { - for (Comment comment : allComments) { - boolean myLike = isCommentMyLiked(comment.getId(), guestOrApiMember); - int likeCount = likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT); - Member member = comment.getMember(); - VoteOption option = member.getVoteOnTalkPick(talkPick) - .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; - comment.setIsBest(likeCount == maxLikes); - - BestCommentResponse response = validateImgUrl(member, comment, option, likeCount, myLike); - - if (comment.getIsBest()) { - bestComments.add(response); - } else { - otherComments.add(response); - } + List bestComments = new ArrayList<>(); + List otherComments = new ArrayList<>(); + + for (Comment comment : allComments) { + BestCommentResponse response = processFindBestComments(comment, talkPick, guestOrApiMember, maxLikes); + if (comment.getIsBest()) { + bestComments.add(response); + } else { + otherComments.add(response); } } + // 정렬 bestComments.sort(Comparator.comparing(BestCommentResponse::getCreatedAt).reversed()); otherComments.sort(Comparator.comparing(BestCommentResponse::getCreatedAt).reversed()); + // 결과 병합 List result = new ArrayList<>(); result.addAll(bestComments); result.addAll(otherComments); + // 페이징 처리 int start = (int) pageable.getOffset(); int end = Math.min((start + pageable.getPageSize()), result.size()); - return new PageImpl<>(result.subList(start, end), pageable, result.size()); } - private BestCommentResponse validateImgUrl(Member member, Comment comment, VoteOption option, - int likeCount, boolean myLike) { - if (member.getProfileImgId() != null) { - String imgUrl = fileRepository.findById(member.getProfileImgId()) - .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)) - .getImgUrl(); - return BestCommentResponse.fromEntity(comment, option, imgUrl, likeCount, myLike); + private BestCommentResponse processFindBestComments(Comment comment, TalkPick talkPick, + GuestOrApiMember guestOrApiMember, int maxLikes) { + boolean myLike = isCommentMyLiked(comment.getId(), guestOrApiMember); + int likeCount = likeRepository.countByResourceIdAndLikeType(comment.getId(), LikeType.COMMENT); + Member member = comment.getMember(); + VoteOption option = member.getVoteOnTalkPick(talkPick) + .isPresent() ? member.getVoteOnTalkPick(talkPick).get().getVoteOption() : null; + + // isBest 여부 설정 + comment.setIsBest(likeCount >= MIN_COUNT_FOR_BEST_COMMENT || likeCount == maxLikes); + String imgUrl = fetchProfileImgUrl(member); + + // BestCommentResponse 생성 + return BestCommentResponse.fromEntity(comment, option, imgUrl, likeCount, myLike); + } + + // 프로필 이미지 URL 조회 + private String fetchProfileImgUrl(Member member) { + if (member.getProfileImgId() == null) { + return null; } - return BestCommentResponse.fromEntity(comment, option, null, likeCount, myLike); + return fileRepository.findById(member.getProfileImgId()) + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)) + .getImgUrl(); } public void updateComment(Long commentId, Long talkPickId, String content, ApiMember apiMember) { @@ -336,7 +326,7 @@ private void sendReplyNotification(Comment parentComment) { // 첫 답글 알림 if ((isFirstReplyFromOther && !parentComment.getIsNotifiedForFirstReply()) && !notificationHistory.getOrDefault(firstReplyKey, false)) { - notificationService.sendTalkPickNotification(parentCommentAuthor,talkPick, + notificationService.sendTalkPickNotification(parentCommentAuthor, talkPick, category, FIRST_COMMENT_REPLY.getMessage()); parentComment.setIsNotifiedForFirstReplyTrue(); // 50, 100개 답글 알림 diff --git a/src/main/java/balancetalk/file/domain/File.java b/src/main/java/balancetalk/file/domain/File.java index 6db6d04bd..a10d2175e 100644 --- a/src/main/java/balancetalk/file/domain/File.java +++ b/src/main/java/balancetalk/file/domain/File.java @@ -1,5 +1,7 @@ package balancetalk.file.domain; +import static balancetalk.file.domain.FileType.FRIENDS; + import balancetalk.global.common.BaseTimeEntity; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -71,4 +73,8 @@ public String getS3Key() { public boolean isUnmapped() { return directoryPath.endsWith("temp/") && resourceId == null; } + + public boolean isUploadedByMember() { + return fileType != FRIENDS; + } } diff --git a/src/main/java/balancetalk/game/application/GameService.java b/src/main/java/balancetalk/game/application/GameService.java index b4c3c48b2..4c2494932 100644 --- a/src/main/java/balancetalk/game/application/GameService.java +++ b/src/main/java/balancetalk/game/application/GameService.java @@ -15,7 +15,6 @@ import balancetalk.game.dto.GameDto.CreateGameMainTagRequest; import balancetalk.game.dto.GameDto.CreateOrUpdateGame; import balancetalk.game.dto.GameDto.GameDetailResponse; -import balancetalk.game.dto.GameOptionDto; import balancetalk.game.dto.GameSetDto.CreateGameSetRequest; import balancetalk.game.dto.GameSetDto.GameSetDetailResponse; import balancetalk.game.dto.GameSetDto.GameSetResponse; @@ -29,7 +28,6 @@ import balancetalk.vote.domain.GameVote; import balancetalk.vote.domain.VoteOption; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -67,9 +65,22 @@ public Long createBalanceGameSet(final CreateGameSetRequest request, final ApiMe } gameSet.addGames(games); - gameSetRepository.save(gameSet); - processFiles(request.getGames(), games); - return gameSet.getId(); + GameSet savedGameSet = gameSetRepository.save(gameSet); + + for (Game game : savedGameSet.getGames()) { + for (GameOption gameOption : game.getGameOptions()) { + relocateFileIfHasImage(gameOption); + } + } + + return savedGameSet.getId(); + } + + private void relocateFileIfHasImage(GameOption gameOption) { + if (gameOption.hasImage()) { + fileRepository.findById(gameOption.getImgId()) + .ifPresent(file -> fileHandler.relocateFile(file, gameOption.getId(), GAME_OPTION)); + } } @Transactional @@ -83,30 +94,29 @@ public void updateBalanceGame(Long gameSetId, UpdateGameSetRequest request, ApiM .map(gameRequest -> gameRequest.toEntity(fileRepository)) .toList(); - gameSet.updateGameSetRequest(request.getTitle(), mainTag, request.getSubTag(), newGames); - processFiles(request.getGames(), gameSet.getGames()); - } - - private void processFiles(List gameRequests, List games) { - for (int i = 0; i < gameRequests.size(); i++) { - CreateOrUpdateGame gameRequest = gameRequests.get(i); - Game game = games.get(i); - - for (int j = 0; j < gameRequest.getGameOptions().size(); j++) { - GameOptionDto gameOptionDto = gameRequest.getGameOptions().get(j); - GameOption gameOption = game.getGameOptions().get(j); - - if (gameOptionDto.getFileId() == null) { - continue; + List oldGames = gameSet.getGames(); + for (int i = 0; i < 10; i++) { + Game oldGame = oldGames.get(i); + Game newGame = newGames.get(i); + List oldGameGameOptions = oldGame.getGameOptions(); + List newGameGameOptions = newGame.getGameOptions(); + for (int j = 0; j < 2; j++) { + GameOption oldGameOption = oldGameGameOptions.get(j); + GameOption newGameOption = newGameGameOptions.get(j); + if (newGameOption.hasImage()) { + if (oldGameOption.hasImage()) { + File oldFile = fileRepository.findById(oldGameOption.getImgId()) + .orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_FILE)); + fileHandler.deleteFile(oldFile); + } + File newFile = fileRepository.findById(newGameOption.getImgId()) + .orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_FILE)); + fileHandler.relocateFile(newFile, oldGameOption.getId(), GAME_OPTION); } - - fileRepository.findById(gameOptionDto.getFileId()) - .ifPresent( - file -> fileHandler.relocateFiles(Collections.singletonList(file), gameOption.getId(), - GAME_OPTION)); - } } + + gameSet.updateGameSetRequest(request.getTitle(), mainTag, request.getSubTag(), newGames); } @Transactional(readOnly = true) diff --git a/src/main/java/balancetalk/game/application/TempGameService.java b/src/main/java/balancetalk/game/application/TempGameService.java index 73be235d8..ff706787d 100644 --- a/src/main/java/balancetalk/game/application/TempGameService.java +++ b/src/main/java/balancetalk/game/application/TempGameService.java @@ -50,7 +50,7 @@ public void createTempGame(CreateTempGameSetRequest request, ApiMember apiMember if (member.hasTempGameSet()) { // 기존 임시저장이 존재하는 경우 TempGameSet existGame = member.getTempGameSet(); - existGame.updateTempGameSet(request.getTitle(), newTempGames); + existGame.updateTempGameSet(request.getTitle(), request.getSubTag(), mainTag, newTempGames); processTempGameFiles(tempGames, existGame.getTempGames()); return; } diff --git a/src/main/java/balancetalk/game/domain/GameOption.java b/src/main/java/balancetalk/game/domain/GameOption.java index 8aef95a0e..ff4cd9630 100644 --- a/src/main/java/balancetalk/game/domain/GameOption.java +++ b/src/main/java/balancetalk/game/domain/GameOption.java @@ -41,6 +41,8 @@ public class GameOption { @Size(max = 30) private String name; + private Long imgId; + private String imgUrl; @Size(max = 50) @@ -65,6 +67,7 @@ public void addGame(Game game) { } public void updateGameOption(GameOption newGameOption) { + this.imgId = newGameOption.getImgId(); this.imgUrl = newGameOption.getImgUrl(); this.name = newGameOption.getName(); this.description = newGameOption.getDescription(); @@ -81,4 +84,8 @@ public void increaseVotesCount() { public void decreaseVotesCount() { this.votesCount--; } + + public boolean hasImage() { + return imgUrl != null && imgId != null; + } } diff --git a/src/main/java/balancetalk/game/domain/TempGameSet.java b/src/main/java/balancetalk/game/domain/TempGameSet.java index 589ab86d3..356ebf467 100644 --- a/src/main/java/balancetalk/game/domain/TempGameSet.java +++ b/src/main/java/balancetalk/game/domain/TempGameSet.java @@ -62,8 +62,10 @@ public void addGames(List tempGames) { }); } - public void updateTempGameSet(String title, List newTempGames) { + public void updateTempGameSet(String title, String subTag, MainTag mainTag, List newTempGames) { this.title = title; + this.subTag = subTag; + this.mainTag = mainTag; IntStream.range(0, this.tempGames.size()).forEach(i -> { TempGame existingGame = this.tempGames.get(i); TempGame newGame = newTempGames.get(i); diff --git a/src/main/java/balancetalk/game/dto/GameOptionDto.java b/src/main/java/balancetalk/game/dto/GameOptionDto.java index 0d12693c6..428a7936a 100644 --- a/src/main/java/balancetalk/game/dto/GameOptionDto.java +++ b/src/main/java/balancetalk/game/dto/GameOptionDto.java @@ -56,6 +56,7 @@ public GameOption toEntity(FileRepository fileRepository) { } return GameOption.builder() .name(name) + .imgId(fileId) .imgUrl(newImgUrl) .description(description) .optionType(optionType) diff --git a/src/main/java/balancetalk/member/application/MemberService.java b/src/main/java/balancetalk/member/application/MemberService.java index e14e150fa..fecb0d240 100644 --- a/src/main/java/balancetalk/member/application/MemberService.java +++ b/src/main/java/balancetalk/member/application/MemberService.java @@ -68,7 +68,9 @@ public void join(final JoinRequest joinRequest) { if (joinRequest.hasProfileImgId()) { File newProfileImgFile = fileRepository.findById(joinRequest.getProfileImgId()) .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)); - fileHandler.relocateFile(newProfileImgFile, savedMember.getId(), FileType.MEMBER); + if (newProfileImgFile.isUploadedByMember()) { + fileHandler.relocateFile(newProfileImgFile, savedMember.getId(), FileType.MEMBER); + } } } @@ -168,14 +170,18 @@ public void updateMemberInformation(MemberUpdateRequest memberUpdateRequest, Api if (memberUpdateRequest.getProfileImgId() != null) { if (member.hasProfileImgId()) { - File file = fileRepository.findById(member.getProfileImgId()) + File oldProfileImgFile = fileRepository.findById(member.getProfileImgId()) .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)); - fileHandler.deleteFile(file); + if (oldProfileImgFile.isUploadedByMember()) { + fileHandler.deleteFile(oldProfileImgFile); + } } - member.updateImageId(memberUpdateRequest.getProfileImgId()); - File file = fileRepository.findById(member.getProfileImgId()) + File newProfileImgFile = fileRepository.findById(memberUpdateRequest.getProfileImgId()) .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_FILE)); - fileHandler.relocateFile(file, member.getId(), FileType.MEMBER); + member.updateImageId(newProfileImgFile.getId()); + if (newProfileImgFile.isUploadedByMember()) { + fileHandler.relocateFile(newProfileImgFile, member.getId(), FileType.MEMBER); + } } if (memberUpdateRequest.getNickname() != null) { @@ -190,8 +196,11 @@ private void validateSameNickname(MemberUpdateRequest memberUpdateRequest, Membe } } - public boolean verifyPassword(String password, ApiMember apiMember) { + public void verifyPassword(String password, ApiMember apiMember) { Member member = apiMember.toMember(memberRepository); - return passwordEncoder.matches(password, member.getPassword()); + + if (!passwordEncoder.matches(password, member.getPassword())) { + throw new BalanceTalkException(PASSWORD_MISMATCH); + } } } diff --git a/src/main/java/balancetalk/member/presentation/MemberController.java b/src/main/java/balancetalk/member/presentation/MemberController.java index 7208efd64..0c266849d 100644 --- a/src/main/java/balancetalk/member/presentation/MemberController.java +++ b/src/main/java/balancetalk/member/presentation/MemberController.java @@ -85,10 +85,10 @@ public void updateMemberInfo(@RequestBody @Valid MemberUpdateRequest memberUpdat memberService.updateMemberInformation(memberUpdateRequest, apiMember); } - @GetMapping("/verify-password") + @PostMapping("/verify-password") @Operation(summary = "비밀번호 검증", description = "회원의 비밀번호를 검증한다.") - public boolean validatePassword(@RequestParam @NotBlank String password, + public void validatePassword(@RequestParam @NotBlank String password, @Parameter(hidden = true) @AuthPrincipal ApiMember apiMember) { - return memberService.verifyPassword(password, apiMember); + memberService.verifyPassword(password, apiMember); } } diff --git a/src/main/java/balancetalk/talkpick/dto/TempTalkPickDto.java b/src/main/java/balancetalk/talkpick/dto/TempTalkPickDto.java index 0383d05a4..aee5d5ddc 100644 --- a/src/main/java/balancetalk/talkpick/dto/TempTalkPickDto.java +++ b/src/main/java/balancetalk/talkpick/dto/TempTalkPickDto.java @@ -2,6 +2,7 @@ import balancetalk.member.domain.Member; import balancetalk.talkpick.domain.TempTalkPick; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -41,6 +42,7 @@ public TempTalkPick toEntity(Member member) { .build(); } + @JsonIgnore public boolean isNewRequest() { return !isLoaded; }