diff --git a/src/main/java/com/swyp/app/domain/battle/service/BattleServiceImpl.java b/src/main/java/com/swyp/app/domain/battle/service/BattleServiceImpl.java index 1bba412..ea0671a 100644 --- a/src/main/java/com/swyp/app/domain/battle/service/BattleServiceImpl.java +++ b/src/main/java/com/swyp/app/domain/battle/service/BattleServiceImpl.java @@ -137,7 +137,7 @@ public BattleUserDetailResponse getBattleDetail(Long battleId) { List allTags = getTagsByBattle(battle); List options = battleOptionRepository.findByBattle(battle); - String voteStatus = voteRepository.findByBattleAndUserId(battle, 1L) + String voteStatus = voteRepository.findByBattleIdAndUserId(battleId, 1L) .map(v -> v.getPostVoteOption() != null ? v.getPostVoteOption().getLabel().name() : "NONE") .orElse("NONE"); diff --git a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveCommentController.java b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveCommentController.java index d869a23..f7c78c9 100644 --- a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveCommentController.java +++ b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveCommentController.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -33,10 +34,9 @@ public class PerspectiveCommentController { @PostMapping("/perspectives/{perspectiveId}/comments") public ApiResponse createComment( @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId, @RequestBody @Valid CreateCommentRequest request ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(commentService.createComment(perspectiveId, userId, request)); } @@ -44,11 +44,10 @@ public ApiResponse createComment( @GetMapping("/perspectives/{perspectiveId}/comments") public ApiResponse getComments( @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId, @RequestParam(required = false) String cursor, @RequestParam(required = false) Integer size ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(commentService.getComments(perspectiveId, userId, cursor, size)); } @@ -56,10 +55,9 @@ public ApiResponse getComments( @DeleteMapping("/perspectives/{perspectiveId}/comments/{commentId}") public ApiResponse deleteComment( @PathVariable Long perspectiveId, - @PathVariable Long commentId + @PathVariable Long commentId, + @AuthenticationPrincipal Long userId ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; commentService.deleteComment(perspectiveId, commentId, userId); return ApiResponse.onSuccess(null); } @@ -69,10 +67,9 @@ public ApiResponse deleteComment( public ApiResponse updateComment( @PathVariable Long perspectiveId, @PathVariable Long commentId, + @AuthenticationPrincipal Long userId, @RequestBody @Valid UpdateCommentRequest request ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(commentService.updateComment(perspectiveId, commentId, userId, request)); } } diff --git a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveController.java b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveController.java index 4ad3125..e1e4bc1 100644 --- a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveController.java +++ b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveController.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -30,15 +31,13 @@ public class PerspectiveController { private final PerspectiveService perspectiveService; - // TODO: Prevote 의 여부를 Vote 도메인 개발 이후 교체 @Operation(summary = "관점 생성", description = "특정 배틀에 대한 관점을 생성합니다. 사전 투표가 완료된 경우에만 가능합니다.") @PostMapping("/battles/{battleId}/perspectives") public ApiResponse createPerspective( @PathVariable Long battleId, + @AuthenticationPrincipal Long userId, @RequestBody @Valid CreatePerspectiveRequest request ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(perspectiveService.createPerspective(battleId, userId, request)); } @@ -46,37 +45,36 @@ public ApiResponse createPerspective( @GetMapping("/battles/{battleId}/perspectives") public ApiResponse getPerspectives( @PathVariable Long battleId, + @AuthenticationPrincipal Long userId, @RequestParam(required = false) String cursor, @RequestParam(required = false) Integer size, @RequestParam(required = false) String optionLabel ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(perspectiveService.getPerspectives(battleId, userId, cursor, size, optionLabel)); } @Operation(summary = "내 PENDING 관점 조회", description = "특정 배틀에서 내가 작성한 관점이 PENDING 상태인 경우 반환합니다. PENDING 관점이 없으면 404를 반환합니다.") @GetMapping("/battles/{battleId}/perspectives/me/pending") - public ApiResponse getMyPendingPerspective(@PathVariable Long battleId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse getMyPendingPerspective( + @PathVariable Long battleId, + @AuthenticationPrincipal Long userId) { return ApiResponse.onSuccess(perspectiveService.getMyPendingPerspective(battleId, userId)); } @Operation(summary = "관점 삭제", description = "본인이 작성한 관점을 삭제합니다.") @DeleteMapping("/perspectives/{perspectiveId}") - public ApiResponse deletePerspective(@PathVariable Long perspectiveId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse deletePerspective( + @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId) { perspectiveService.deletePerspective(perspectiveId, userId); return ApiResponse.onSuccess(null); } @Operation(summary = "관점 검수 재시도", description = "검수 실패(MODERATION_FAILED) 상태의 관점에 대해 GPT 검수를 다시 요청합니다.") @PostMapping("/perspectives/{perspectiveId}/moderation/retry") - public ApiResponse retryModeration(@PathVariable Long perspectiveId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse retryModeration( + @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId) { perspectiveService.retryModeration(perspectiveId, userId); return ApiResponse.onSuccess(null); } @@ -85,10 +83,9 @@ public ApiResponse retryModeration(@PathVariable Long perspectiveId) { @PatchMapping("/perspectives/{perspectiveId}") public ApiResponse updatePerspective( @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId, @RequestBody @Valid UpdatePerspectiveRequest request ) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(perspectiveService.updatePerspective(perspectiveId, userId, request)); } } diff --git a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveLikeController.java b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveLikeController.java index c73d0f4..abe7cc7 100644 --- a/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveLikeController.java +++ b/src/main/java/com/swyp/app/domain/perspective/controller/PerspectiveLikeController.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -30,17 +31,17 @@ public ApiResponse getLikeCount(@PathVariable Long perspectiv @Operation(summary = "좋아요 등록", description = "특정 관점에 좋아요를 등록합니다.") @PostMapping("/perspectives/{perspectiveId}/likes") - public ApiResponse addLike(@PathVariable Long perspectiveId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse addLike( + @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId) { return ApiResponse.onSuccess(likeService.addLike(perspectiveId, userId)); } @Operation(summary = "좋아요 취소", description = "특정 관점에 등록한 좋아요를 취소합니다.") @DeleteMapping("/perspectives/{perspectiveId}/likes") - public ApiResponse removeLike(@PathVariable Long perspectiveId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse removeLike( + @PathVariable Long perspectiveId, + @AuthenticationPrincipal Long userId) { return ApiResponse.onSuccess(likeService.removeLike(perspectiveId, userId)); } } diff --git a/src/main/java/com/swyp/app/domain/perspective/entity/Perspective.java b/src/main/java/com/swyp/app/domain/perspective/entity/Perspective.java index 622633e..48ece7a 100644 --- a/src/main/java/com/swyp/app/domain/perspective/entity/Perspective.java +++ b/src/main/java/com/swyp/app/domain/perspective/entity/Perspective.java @@ -1,14 +1,17 @@ package com.swyp.app.domain.perspective.entity; +import com.swyp.app.domain.battle.entity.Battle; +import com.swyp.app.domain.battle.entity.BattleOption; import com.swyp.app.domain.perspective.enums.PerspectiveStatus; +import com.swyp.app.domain.user.entity.User; import com.swyp.app.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; @@ -25,17 +28,17 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Perspective extends BaseEntity { - // TODO: Battle 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "battle_id") 로 교체 - @Column(name = "battle_id", nullable = false) - private Long battleId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "battle_id", nullable = false) + private Battle battle; - // TODO: User 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") 로 교체 - @Column(name = "user_id", nullable = false) - private Long userId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; - // TODO: BattleOption 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "option_id") 로 교체 - @Column(name = "option_id", nullable = false) - private Long optionId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "option_id", nullable = false) + private BattleOption option; @Column(nullable = false, columnDefinition = "TEXT") private String content; @@ -51,10 +54,10 @@ public class Perspective extends BaseEntity { private PerspectiveStatus status; @Builder - private Perspective(Long battleId, Long userId, Long optionId, String content) { - this.battleId = battleId; - this.userId = userId; - this.optionId = optionId; + private Perspective(Battle battle, User user, BattleOption option, String content) { + this.battle = battle; + this.user = user; + this.option = option; this.content = content; this.likeCount = 0; this.commentCount = 0; diff --git a/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveComment.java b/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveComment.java index 19a940a..bf41727 100644 --- a/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveComment.java +++ b/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveComment.java @@ -1,5 +1,6 @@ package com.swyp.app.domain.perspective.entity; +import com.swyp.app.domain.user.entity.User; import com.swyp.app.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -22,17 +23,17 @@ public class PerspectiveComment extends BaseEntity { @JoinColumn(name = "perspective_id", nullable = false) private Perspective perspective; - // TODO: User 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") 로 교체 - @Column(name = "user_id", nullable = false) - private Long userId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; @Column(nullable = false, columnDefinition = "TEXT") private String content; @Builder - private PerspectiveComment(Perspective perspective, Long userId, String content) { + private PerspectiveComment(Perspective perspective, User user, String content) { this.perspective = perspective; - this.userId = userId; + this.user = user; this.content = content; } diff --git a/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveLike.java b/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveLike.java index 3850c32..db399a6 100644 --- a/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveLike.java +++ b/src/main/java/com/swyp/app/domain/perspective/entity/PerspectiveLike.java @@ -1,7 +1,7 @@ package com.swyp.app.domain.perspective.entity; +import com.swyp.app.domain.user.entity.User; import com.swyp.app.global.common.BaseEntity; -import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; @@ -26,13 +26,13 @@ public class PerspectiveLike extends BaseEntity { @JoinColumn(name = "perspective_id", nullable = false) private Perspective perspective; - // TODO: User 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") 로 교체 - @Column(name = "user_id", nullable = false) - private Long userId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; @Builder - private PerspectiveLike(Perspective perspective, Long userId) { + private PerspectiveLike(Perspective perspective, User user) { this.perspective = perspective; - this.userId = userId; + this.user = user; } } diff --git a/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveCommentRepository.java b/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveCommentRepository.java index e28e877..ad44196 100644 --- a/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveCommentRepository.java +++ b/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveCommentRepository.java @@ -16,8 +16,7 @@ public interface PerspectiveCommentRepository extends JpaRepository findByPerspectiveAndCreatedAtBeforeOrderByCreatedAtDesc(Perspective perspective, LocalDateTime cursor, Pageable pageable); - // MypageService: 사용자 댓글 활동 조회 (offset 페이지네이션) - @Query("SELECT c FROM PerspectiveComment c JOIN FETCH c.perspective WHERE c.userId = :userId ORDER BY c.createdAt DESC") + @Query("SELECT c FROM PerspectiveComment c JOIN FETCH c.perspective WHERE c.user.id = :userId ORDER BY c.createdAt DESC") List findByUserIdOrderByCreatedAtDesc(@Param("userId") Long userId, Pageable pageable); long countByUserId(Long userId); diff --git a/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveLikeRepository.java b/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveLikeRepository.java index 267a6ba..f71877c 100644 --- a/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveLikeRepository.java +++ b/src/main/java/com/swyp/app/domain/perspective/repository/PerspectiveLikeRepository.java @@ -18,8 +18,7 @@ public interface PerspectiveLikeRepository extends JpaRepository findByUserIdOrderByCreatedAtDesc(@Param("userId") Long userId, Pageable pageable); long countByUserId(Long userId); diff --git a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveCommentService.java b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveCommentService.java index df7fc6e..88ded3c 100644 --- a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveCommentService.java +++ b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveCommentService.java @@ -10,6 +10,8 @@ import com.swyp.app.domain.perspective.repository.PerspectiveCommentRepository; import com.swyp.app.domain.perspective.repository.PerspectiveRepository; import com.swyp.app.domain.user.dto.response.UserSummary; +import com.swyp.app.domain.user.entity.User; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.domain.user.service.UserService; import com.swyp.app.global.common.exception.CustomException; import com.swyp.app.global.common.exception.ErrorCode; @@ -30,25 +32,28 @@ public class PerspectiveCommentService { private final PerspectiveRepository perspectiveRepository; private final PerspectiveCommentRepository commentRepository; + private final UserRepository userRepository; private final UserService userQueryService; @Transactional public CreateCommentResponse createComment(Long perspectiveId, Long userId, CreateCommentRequest request) { Perspective perspective = findPerspectiveById(perspectiveId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); PerspectiveComment comment = PerspectiveComment.builder() .perspective(perspective) - .userId(userId) + .user(user) .content(request.content()) .build(); commentRepository.save(comment); perspective.incrementCommentCount(); - UserSummary user = userQueryService.findSummaryById(userId); + UserSummary userSummary = userQueryService.findSummaryById(userId); return new CreateCommentResponse( comment.getId(), - new CreateCommentResponse.UserSummary(user.userTag(), user.nickname(), user.characterType()), + new CreateCommentResponse.UserSummary(userSummary.userTag(), userSummary.nickname(), userSummary.characterType()), comment.getContent(), comment.getCreatedAt() ); @@ -67,12 +72,12 @@ public CommentListResponse getComments(Long perspectiveId, Long userId, String c List items = comments.stream() .map(c -> { - UserSummary user = userQueryService.findSummaryById(c.getUserId()); + UserSummary author = userQueryService.findSummaryById(c.getUser().getId()); return new CommentListResponse.Item( c.getId(), - new CommentListResponse.UserSummary(user.userTag(), user.nickname(), user.characterType()), + new CommentListResponse.UserSummary(author.userTag(), author.nickname(), author.characterType()), c.getContent(), - c.getUserId().equals(userId), + c.getUser().getId().equals(userId), c.getCreatedAt() ); }) @@ -116,7 +121,7 @@ private PerspectiveComment findCommentById(Long commentId) { } private void validateOwnership(PerspectiveComment comment, Long userId) { - if (!comment.getUserId().equals(userId)) { + if (!comment.getUser().getId().equals(userId)) { throw new CustomException(ErrorCode.COMMENT_FORBIDDEN); } } diff --git a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveLikeService.java b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveLikeService.java index 3b77392..24b3e95 100644 --- a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveLikeService.java +++ b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveLikeService.java @@ -6,6 +6,8 @@ import com.swyp.app.domain.perspective.entity.PerspectiveLike; import com.swyp.app.domain.perspective.repository.PerspectiveLikeRepository; import com.swyp.app.domain.perspective.repository.PerspectiveRepository; +import com.swyp.app.domain.user.entity.User; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.global.common.exception.CustomException; import com.swyp.app.global.common.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -19,6 +21,7 @@ public class PerspectiveLikeService { private final PerspectiveRepository perspectiveRepository; private final PerspectiveLikeRepository likeRepository; + private final UserRepository userRepository; public LikeCountResponse getLikeCount(Long perspectiveId) { Perspective perspective = findPerspectiveById(perspectiveId); @@ -29,8 +32,10 @@ public LikeCountResponse getLikeCount(Long perspectiveId) { @Transactional public LikeResponse addLike(Long perspectiveId, Long userId) { Perspective perspective = findPerspectiveById(perspectiveId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - if (perspective.getUserId().equals(userId)) { + if (perspective.getUser().getId().equals(userId)) { throw new CustomException(ErrorCode.LIKE_SELF_FORBIDDEN); } @@ -40,7 +45,7 @@ public LikeResponse addLike(Long perspectiveId, Long userId) { likeRepository.save(PerspectiveLike.builder() .perspective(perspective) - .userId(userId) + .user(user) .build()); perspective.incrementLikeCount(); diff --git a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveService.java b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveService.java index 4e75278..f910766 100644 --- a/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveService.java +++ b/src/main/java/com/swyp/app/domain/perspective/service/PerspectiveService.java @@ -1,9 +1,12 @@ package com.swyp.app.domain.perspective.service; +import com.swyp.app.domain.battle.entity.Battle; import com.swyp.app.domain.battle.entity.BattleOption; import com.swyp.app.domain.battle.enums.BattleOptionLabel; import com.swyp.app.domain.battle.service.BattleService; import com.swyp.app.domain.perspective.enums.PerspectiveStatus; +import com.swyp.app.domain.user.entity.User; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.domain.perspective.dto.request.CreatePerspectiveRequest; import com.swyp.app.domain.perspective.dto.request.UpdatePerspectiveRequest; import com.swyp.app.domain.perspective.dto.response.CreatePerspectiveResponse; @@ -38,22 +41,25 @@ public class PerspectiveService { private final BattleService battleService; private final VoteService voteService; private final UserService userQueryService; + private final UserRepository userRepository; private final GptModerationService gptModerationService; @Transactional public CreatePerspectiveResponse createPerspective(Long battleId, Long userId, CreatePerspectiveRequest request) { - battleService.findById(battleId); + Battle battle = battleService.findById(battleId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); if (perspectiveRepository.existsByBattleIdAndUserId(battleId, userId)) { throw new CustomException(ErrorCode.PERSPECTIVE_ALREADY_EXISTS); } - Long optionId = voteService.findPreVoteOptionId(battleId, userId); + BattleOption option = voteService.findPreVoteOption(battleId, userId); Perspective perspective = Perspective.builder() - .battleId(battleId) - .userId(userId) - .optionId(optionId) + .battle(battle) + .user(user) + .option(option) .content(request.content()) .build(); @@ -84,8 +90,8 @@ public PerspectiveListResponse getPerspectives(Long battleId, Long userId, Strin List items = perspectives.stream() .map(p -> { - UserSummary user = userQueryService.findSummaryById(p.getUserId()); - BattleOption option = battleService.findOptionById(p.getOptionId()); + UserSummary user = userQueryService.findSummaryById(p.getUser().getId()); + BattleOption option = p.getOption(); boolean isLiked = perspectiveLikeRepository.existsByPerspectiveAndUserId(p, userId); return new PerspectiveListResponse.Item( p.getId(), @@ -154,7 +160,7 @@ private Perspective findPerspectiveById(Long perspectiveId) { } private void validateOwnership(Perspective perspective, Long userId) { - if (!perspective.getUserId().equals(userId)) { + if (!perspective.getUser().getId().equals(userId)) { throw new CustomException(ErrorCode.PERSPECTIVE_FORBIDDEN); } } diff --git a/src/main/java/com/swyp/app/domain/reward/service/AdMobRewardServiceImpl.java b/src/main/java/com/swyp/app/domain/reward/service/AdMobRewardServiceImpl.java index 70114fa..365a6c1 100644 --- a/src/main/java/com/swyp/app/domain/reward/service/AdMobRewardServiceImpl.java +++ b/src/main/java/com/swyp/app/domain/reward/service/AdMobRewardServiceImpl.java @@ -52,9 +52,6 @@ public String processReward(AdMobRewardRequest request) { adRewardHistoryRepository.save(history); - // 6. TODO: 작업 중인 포인트 합산 로직 호출 지점 - // user.addPoint(request.reward_amount()); - log.info("보상 지급 완료: user={}, amount={}", user.getId(), request.reward_amount()); return "OK"; } diff --git a/src/main/java/com/swyp/app/domain/scenario/controller/ScenarioController.java b/src/main/java/com/swyp/app/domain/scenario/controller/ScenarioController.java index 59c7d98..926b048 100644 --- a/src/main/java/com/swyp/app/domain/scenario/controller/ScenarioController.java +++ b/src/main/java/com/swyp/app/domain/scenario/controller/ScenarioController.java @@ -3,7 +3,7 @@ import com.swyp.app.domain.scenario.dto.request.ScenarioCreateRequest; import com.swyp.app.domain.scenario.dto.request.ScenarioStatusUpdateRequest; import com.swyp.app.domain.scenario.dto.response.AdminDeleteResponse; -import com.swyp.app.domain.scenario.dto.response.AdminScenarioDetailResponse; // 🚀 추가 (상세 조회용) +import com.swyp.app.domain.scenario.dto.response.AdminScenarioDetailResponse; import com.swyp.app.domain.scenario.dto.response.AdminScenarioResponse; import com.swyp.app.domain.scenario.dto.response.UserScenarioResponse; import com.swyp.app.domain.scenario.service.ScenarioService; diff --git a/src/main/java/com/swyp/app/domain/scenario/service/ScenarioServiceImpl.java b/src/main/java/com/swyp/app/domain/scenario/service/ScenarioServiceImpl.java index 8dadc05..a5d4b4e 100644 --- a/src/main/java/com/swyp/app/domain/scenario/service/ScenarioServiceImpl.java +++ b/src/main/java/com/swyp/app/domain/scenario/service/ScenarioServiceImpl.java @@ -8,7 +8,7 @@ import com.swyp.app.domain.scenario.dto.request.ScenarioCreateRequest; import com.swyp.app.domain.scenario.dto.request.ScriptRequest; import com.swyp.app.domain.scenario.dto.response.AdminDeleteResponse; -import com.swyp.app.domain.scenario.dto.response.AdminScenarioDetailResponse; // 🚀 추가 +import com.swyp.app.domain.scenario.dto.response.AdminScenarioDetailResponse; import com.swyp.app.domain.scenario.dto.response.AdminScenarioResponse; import com.swyp.app.domain.scenario.dto.response.UserScenarioResponse; import com.swyp.app.domain.scenario.entity.InteractiveOption; diff --git a/src/main/java/com/swyp/app/domain/user/entity/CreditHistory.java b/src/main/java/com/swyp/app/domain/user/entity/CreditHistory.java index ceeea05..e3000ff 100644 --- a/src/main/java/com/swyp/app/domain/user/entity/CreditHistory.java +++ b/src/main/java/com/swyp/app/domain/user/entity/CreditHistory.java @@ -6,7 +6,10 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; @@ -22,8 +25,9 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class CreditHistory extends BaseEntity { - @Column(name = "user_id", nullable = false) - private Long userId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; @Enumerated(EnumType.STRING) @Column(name = "credit_type", nullable = false, length = 30) @@ -36,8 +40,8 @@ public class CreditHistory extends BaseEntity { private Long referenceId; @Builder - private CreditHistory(Long userId, CreditType creditType, int amount, Long referenceId) { - this.userId = userId; + private CreditHistory(User user, CreditType creditType, int amount, Long referenceId) { + this.user = user; this.creditType = creditType; this.amount = amount; this.referenceId = referenceId; diff --git a/src/main/java/com/swyp/app/domain/user/repository/CreditHistoryRepository.java b/src/main/java/com/swyp/app/domain/user/repository/CreditHistoryRepository.java index a860eef..775b23c 100644 --- a/src/main/java/com/swyp/app/domain/user/repository/CreditHistoryRepository.java +++ b/src/main/java/com/swyp/app/domain/user/repository/CreditHistoryRepository.java @@ -8,7 +8,7 @@ public interface CreditHistoryRepository extends JpaRepository { - @Query("SELECT COALESCE(SUM(c.amount), 0) FROM CreditHistory c WHERE c.userId = :userId") + @Query("SELECT COALESCE(SUM(c.amount), 0) FROM CreditHistory c WHERE c.user.id = :userId") int sumAmountByUserId(@Param("userId") Long userId); boolean existsByUserIdAndCreditTypeAndReferenceId(Long userId, CreditType creditType, Long referenceId); diff --git a/src/main/java/com/swyp/app/domain/user/service/CreditService.java b/src/main/java/com/swyp/app/domain/user/service/CreditService.java index a9c9ce3..7704791 100644 --- a/src/main/java/com/swyp/app/domain/user/service/CreditService.java +++ b/src/main/java/com/swyp/app/domain/user/service/CreditService.java @@ -5,6 +5,7 @@ import com.swyp.app.domain.user.entity.User; import com.swyp.app.domain.user.enums.CreditType; import com.swyp.app.domain.user.repository.CreditHistoryRepository; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.global.common.exception.CustomException; import com.swyp.app.global.common.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ public class CreditService { private final CreditHistoryRepository creditHistoryRepository; + private final UserRepository userRepository; private final UserService userService; /** @@ -50,8 +52,11 @@ public void addCredit(Long userId, CreditType creditType, Long referenceId) { public void addCredit(Long userId, CreditType creditType, int amount, Long referenceId) { validateReferenceId(referenceId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + CreditHistory history = CreditHistory.builder() - .userId(userId) + .user(user) .creditType(creditType) .amount(amount) .referenceId(referenceId) diff --git a/src/main/java/com/swyp/app/domain/user/service/MypageService.java b/src/main/java/com/swyp/app/domain/user/service/MypageService.java index fa45e85..3fcd2ba 100644 --- a/src/main/java/com/swyp/app/domain/user/service/MypageService.java +++ b/src/main/java/com/swyp/app/domain/user/service/MypageService.java @@ -193,7 +193,7 @@ private ContentActivityListResponse buildCommentActivities(User user, int pageOf .map(comment -> { Perspective p = comment.getPerspective(); return toActivityItem(comment.getId().toString(), ActivityType.COMMENT, p, - battleMap.get(p.getBattleId()), optionMap.get(p.getOptionId()), + battleMap.get(p.getBattle().getId()), optionMap.get(p.getOption().getId()), comment.getContent(), comment.getCreatedAt()); }) .toList(); @@ -215,7 +215,7 @@ private ContentActivityListResponse buildLikeActivities(User user, int pageOffse .map(like -> { Perspective p = like.getPerspective(); return toActivityItem(like.getId().toString(), ActivityType.LIKE, p, - battleMap.get(p.getBattleId()), optionMap.get(p.getOptionId()), + battleMap.get(p.getBattle().getId()), optionMap.get(p.getOption().getId()), p.getContent(), like.getCreatedAt()); }) .toList(); @@ -229,7 +229,7 @@ private ContentActivityListResponse.ContentActivityItem toActivityItem( String activityId, ActivityType activityType, Perspective perspective, Battle battle, BattleOption option, String content, LocalDateTime createdAt) { - UserSummary author = userService.findSummaryById(perspective.getUserId()); + UserSummary author = userService.findSummaryById(perspective.getUser().getId()); ContentActivityListResponse.AuthorInfo authorInfo = new ContentActivityListResponse.AuthorInfo( author.userTag(), author.nickname(), CharacterType.from(author.characterType()) ); @@ -237,7 +237,7 @@ private ContentActivityListResponse.ContentActivityItem toActivityItem( return new ContentActivityListResponse.ContentActivityItem( activityId, activityType, perspective.getId().toString(), - perspective.getBattleId().toString(), + perspective.getBattle().getId().toString(), battle != null ? battle.getTitle() : null, authorInfo, option != null ? option.getStance() : null, @@ -248,12 +248,12 @@ private ContentActivityListResponse.ContentActivityItem toActivityItem( } private Map loadBattles(List perspectives) { - List battleIds = perspectives.stream().map(Perspective::getBattleId).distinct().toList(); + List battleIds = perspectives.stream().map(p -> p.getBattle().getId()).distinct().toList(); return battleQueryService.findBattlesByIds(battleIds); } private Map loadOptions(List perspectives) { - List optionIds = perspectives.stream().map(Perspective::getOptionId).distinct().toList(); + List optionIds = perspectives.stream().map(p -> p.getOption().getId()).distinct().toList(); return battleQueryService.findOptionsByIds(optionIds); } diff --git a/src/main/java/com/swyp/app/domain/vote/controller/VoteController.java b/src/main/java/com/swyp/app/domain/vote/controller/VoteController.java index 1d703fd..4bdb9b8 100644 --- a/src/main/java/com/swyp/app/domain/vote/controller/VoteController.java +++ b/src/main/java/com/swyp/app/domain/vote/controller/VoteController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @Tag(name = "투표 (Vote)", description = "사전/사후 투표 실행 및 통계, 내 투표 내역 조회 API") @@ -23,9 +24,8 @@ public class VoteController { @PostMapping("/battles/{battleId}/votes/pre") public ApiResponse preVote( @PathVariable Long battleId, + @AuthenticationPrincipal Long userId, @RequestBody VoteRequest request) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(voteService.preVote(battleId, userId, request)); } @@ -33,9 +33,8 @@ public ApiResponse preVote( @PostMapping("/battles/{battleId}/votes/post") public ApiResponse postVote( @PathVariable Long battleId, + @AuthenticationPrincipal Long userId, @RequestBody VoteRequest request) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; return ApiResponse.onSuccess(voteService.postVote(battleId, userId, request)); } @@ -47,9 +46,9 @@ public ApiResponse getVoteStats(@PathVariable Long battleId) @Operation(summary = "내 투표 내역 조회", description = "특정 배틀에 대한 내 사전/사후 투표 내역과 현재 상태를 조회합니다.") @GetMapping("/battles/{battleId}/votes/me") - public ApiResponse getMyVote(@PathVariable Long battleId) { - // TODO: Security 적용 후 @AuthenticationPrincipal로 userId 교체 - Long userId = 1L; + public ApiResponse getMyVote( + @PathVariable Long battleId, + @AuthenticationPrincipal Long userId) { return ApiResponse.onSuccess(voteService.getMyVote(battleId, userId)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/swyp/app/domain/vote/entity/Vote.java b/src/main/java/com/swyp/app/domain/vote/entity/Vote.java index fe9a24b..2c469b1 100644 --- a/src/main/java/com/swyp/app/domain/vote/entity/Vote.java +++ b/src/main/java/com/swyp/app/domain/vote/entity/Vote.java @@ -2,6 +2,7 @@ import com.swyp.app.domain.battle.entity.Battle; import com.swyp.app.domain.battle.entity.BattleOption; +import com.swyp.app.domain.user.entity.User; import com.swyp.app.domain.vote.enums.VoteStatus; import com.swyp.app.global.common.BaseEntity; import jakarta.persistence.Column; @@ -9,9 +10,6 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @@ -26,9 +24,9 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Vote extends BaseEntity { - // TODO: User 엔티티 병합 후 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") 로 교체 - @Column(name = "user_id", nullable = false) - private Long userId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "battle_id", nullable = false) @@ -47,28 +45,26 @@ public class Vote extends BaseEntity { private VoteStatus status; @Builder - private Vote(Long userId, Battle battle, BattleOption preVoteOption, + private Vote(User user, Battle battle, BattleOption preVoteOption, BattleOption postVoteOption, VoteStatus status) { - this.userId = userId; + this.user = user; this.battle = battle; this.preVoteOption = preVoteOption; this.postVoteOption = postVoteOption; this.status = status; } - // 사전 투표 생성 팩토리 메서드 - public static Vote createPreVote(Long userId, Battle battle, BattleOption option) { + public static Vote createPreVote(User user, Battle battle, BattleOption option) { return Vote.builder() - .userId(userId) + .user(user) .battle(battle) .preVoteOption(option) .status(VoteStatus.PRE_VOTED) .build(); } - // 사후 투표 실행 상태 변경 메서드 public void doPostVote(BattleOption postOption) { this.postVoteOption = postOption; this.status = VoteStatus.POST_VOTED; } -} \ No newline at end of file +} diff --git a/src/main/java/com/swyp/app/domain/vote/repository/VoteRepository.java b/src/main/java/com/swyp/app/domain/vote/repository/VoteRepository.java index da6a67e..cd0ed29 100644 --- a/src/main/java/com/swyp/app/domain/vote/repository/VoteRepository.java +++ b/src/main/java/com/swyp/app/domain/vote/repository/VoteRepository.java @@ -5,6 +5,7 @@ import com.swyp.app.domain.battle.enums.BattleOptionLabel; import com.swyp.app.domain.user.entity.User; import com.swyp.app.domain.vote.entity.Vote; +import com.swyp.app.domain.vote.enums.VoteStatus; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -15,11 +16,9 @@ public interface VoteRepository extends JpaRepository { - // ScenarioService : Battle 엔티티 조회 없이 ID만으로 투표 내역 확인 Optional findByBattleIdAndUserId(Long battleId, Long userId); - // VoteService : 이미 조회된 Battle 엔티티를 활용하여 투표 내역 확인 - Optional findByBattleAndUserId(Battle battle, Long userId); + Optional findByBattleAndUser(Battle battle, User user); long countByBattle(Battle battle); @@ -27,36 +26,29 @@ public interface VoteRepository extends JpaRepository { Optional findTopByBattleOrderByUpdatedAtDesc(Battle battle); - // MypageService: 사용자 투표 기록 조회 (offset 페이지네이션) @Query("SELECT v FROM Vote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption " + - "WHERE v.userId = :userId ORDER BY v.createdAt DESC") + "WHERE v.user.id = :userId ORDER BY v.createdAt DESC") List findByUserIdOrderByCreatedAtDesc(@Param("userId") Long userId, Pageable pageable); - // MypageService: 사용자 투표 기록 - voteSide(PRO/CON) 필터 @Query("SELECT v FROM Vote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption " + - "WHERE v.userId = :userId AND v.preVoteOption.label = :label ORDER BY v.createdAt DESC") + "WHERE v.user.id = :userId AND v.preVoteOption.label = :label ORDER BY v.createdAt DESC") List findByUserIdAndPreVoteOptionLabelOrderByCreatedAtDesc( @Param("userId") Long userId, @Param("label") BattleOptionLabel label, Pageable pageable); - // MypageService: 사용자 투표 전체 수 long countByUserId(Long userId); - // MypageService: 사용자 투표 수 - voteSide 필터 - @Query("SELECT COUNT(v) FROM Vote v WHERE v.userId = :userId AND v.preVoteOption.label = :label") + @Query("SELECT COUNT(v) FROM Vote v WHERE v.user.id = :userId AND v.preVoteOption.label = :label") long countByUserIdAndPreVoteOptionLabel(@Param("userId") Long userId, @Param("label") BattleOptionLabel label); - // MypageService (recap): 사후 투표 완료 수 - long countByUserIdAndStatus(Long userId, com.swyp.app.domain.vote.enums.VoteStatus status); + long countByUserIdAndStatus(Long userId, VoteStatus status); - // MypageService (recap): 입장 변경 수 (사전/사후 투표 옵션이 다른 경우) - @Query("SELECT COUNT(v) FROM Vote v WHERE v.userId = :userId AND v.status = 'POST_VOTED' " + + @Query("SELECT COUNT(v) FROM Vote v WHERE v.user.id = :userId AND v.status = 'POST_VOTED' " + "AND v.preVoteOption <> v.postVoteOption") long countOpinionChangesByUserId(@Param("userId") Long userId); - // MypageService (recap): 사용자가 참여한 모든 투표 (배틀 목록 추출용) List findByUserId(Long userId); // MypageService: 철학자 유형 산출용 - 최초 N개 투표 조회 (생성순) - @Query("SELECT v FROM Vote v JOIN FETCH v.battle WHERE v.userId = :userId ORDER BY v.createdAt ASC") + @Query("SELECT v FROM Vote v JOIN FETCH v.battle WHERE v.user.id = :userId ORDER BY v.createdAt ASC") List findByUserIdOrderByCreatedAtAsc(@Param("userId") Long userId, Pageable pageable); } diff --git a/src/main/java/com/swyp/app/domain/vote/service/VoteService.java b/src/main/java/com/swyp/app/domain/vote/service/VoteService.java index ba47ef5..70e95a7 100644 --- a/src/main/java/com/swyp/app/domain/vote/service/VoteService.java +++ b/src/main/java/com/swyp/app/domain/vote/service/VoteService.java @@ -1,5 +1,6 @@ package com.swyp.app.domain.vote.service; +import com.swyp.app.domain.battle.entity.BattleOption; import com.swyp.app.domain.vote.dto.request.VoteRequest; import com.swyp.app.domain.vote.dto.response.MyVoteResponse; import com.swyp.app.domain.vote.dto.response.VoteResultResponse; @@ -7,7 +8,7 @@ public interface VoteService { - Long findPreVoteOptionId(Long battleId, Long userId); + BattleOption findPreVoteOption(Long battleId, Long userId); VoteStatsResponse getVoteStats(Long battleId); diff --git a/src/main/java/com/swyp/app/domain/vote/service/VoteServiceImpl.java b/src/main/java/com/swyp/app/domain/vote/service/VoteServiceImpl.java index df70224..c76898b 100644 --- a/src/main/java/com/swyp/app/domain/vote/service/VoteServiceImpl.java +++ b/src/main/java/com/swyp/app/domain/vote/service/VoteServiceImpl.java @@ -4,6 +4,8 @@ import com.swyp.app.domain.battle.entity.BattleOption; import com.swyp.app.domain.battle.repository.BattleOptionRepository; import com.swyp.app.domain.battle.service.BattleService; +import com.swyp.app.domain.user.entity.User; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.domain.vote.converter.VoteConverter; import com.swyp.app.domain.vote.dto.request.VoteRequest; import com.swyp.app.domain.vote.dto.response.MyVoteResponse; @@ -29,18 +31,21 @@ public class VoteServiceImpl implements VoteService { private final VoteRepository voteRepository; private final BattleService battleService; private final BattleOptionRepository battleOptionRepository; + private final UserRepository userRepository; @Override - public Long findPreVoteOptionId(Long battleId, Long userId) { + public BattleOption findPreVoteOption(Long battleId, Long userId) { Battle battle = battleService.findById(battleId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - Vote vote = voteRepository.findByBattleAndUserId(battle, userId) + Vote vote = voteRepository.findByBattleAndUser(battle, user) .orElseThrow(() -> new CustomException(ErrorCode.VOTE_NOT_FOUND)); if (vote.getPreVoteOption() == null) { throw new CustomException(ErrorCode.PRE_VOTE_REQUIRED); } - return vote.getPreVoteOption().getId(); + return vote.getPreVoteOption(); } @Override @@ -70,8 +75,10 @@ public VoteStatsResponse getVoteStats(Long battleId) { @Override public MyVoteResponse getMyVote(Long battleId, Long userId) { Battle battle = battleService.findById(battleId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - Vote vote = voteRepository.findByBattleAndUserId(battle, userId) + Vote vote = voteRepository.findByBattleAndUser(battle, user) .orElseThrow(() -> new CustomException(ErrorCode.VOTE_NOT_FOUND)); return VoteConverter.toMyVoteResponse(vote); @@ -81,15 +88,16 @@ public MyVoteResponse getMyVote(Long battleId, Long userId) { @Transactional public VoteResultResponse preVote(Long battleId, Long userId, VoteRequest request) { Battle battle = battleService.findById(battleId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); BattleOption option = battleOptionRepository.findById(request.optionId()) .orElseThrow(() -> new CustomException(ErrorCode.BATTLE_OPTION_NOT_FOUND)); - // 이미 투표 내역이 존재하는지 검증 - if (voteRepository.findByBattleAndUserId(battle, userId).isPresent()) { + if (voteRepository.findByBattleAndUser(battle, user).isPresent()) { throw new CustomException(ErrorCode.VOTE_ALREADY_SUBMITTED); } - Vote vote = Vote.createPreVote(userId, battle, option); + Vote vote = Vote.createPreVote(user, battle, option); voteRepository.save(vote); return VoteConverter.toVoteResultResponse(vote); @@ -99,13 +107,14 @@ public VoteResultResponse preVote(Long battleId, Long userId, VoteRequest reques @Transactional public VoteResultResponse postVote(Long battleId, Long userId, VoteRequest request) { Battle battle = battleService.findById(battleId); + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); BattleOption option = battleOptionRepository.findById(request.optionId()) .orElseThrow(() -> new CustomException(ErrorCode.BATTLE_OPTION_NOT_FOUND)); - Vote vote = voteRepository.findByBattleAndUserId(battle, userId) + Vote vote = voteRepository.findByBattleAndUser(battle, user) .orElseThrow(() -> new CustomException(ErrorCode.VOTE_NOT_FOUND)); - // 사전 투표 상태일 때만 사후 투표 가능 if (vote.getStatus() != VoteStatus.PRE_VOTED) { throw new CustomException(ErrorCode.INVALID_VOTE_STATUS); } diff --git a/src/main/java/com/swyp/app/global/config/S3Config.java b/src/main/java/com/swyp/app/global/config/S3Config.java index f2ae86f..cac7a59 100644 --- a/src/main/java/com/swyp/app/global/config/S3Config.java +++ b/src/main/java/com/swyp/app/global/config/S3Config.java @@ -8,7 +8,6 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.presigner.S3Presigner; -// TODO: S3 Presigned URL 정식 구현 시 교체 필요 (임시 구현) @Configuration public class S3Config { diff --git a/src/main/java/com/swyp/app/global/infra/s3/dto/FileUploadResponse.java b/src/main/java/com/swyp/app/global/infra/s3/dto/FileUploadResponse.java index beaf366..4a510ac 100644 --- a/src/main/java/com/swyp/app/global/infra/s3/dto/FileUploadResponse.java +++ b/src/main/java/com/swyp/app/global/infra/s3/dto/FileUploadResponse.java @@ -1,4 +1,3 @@ package com.swyp.app.global.infra.s3.dto; -// TODO: S3 Presigned URL 정식 구현 시 교체 필요 (임시 구현) public record FileUploadResponse(String s3Key, String presignedUrl) {} diff --git a/src/main/java/com/swyp/app/global/infra/s3/service/S3PresignedUrlService.java b/src/main/java/com/swyp/app/global/infra/s3/service/S3PresignedUrlService.java index ca1fe2e..fc7b0be 100644 --- a/src/main/java/com/swyp/app/global/infra/s3/service/S3PresignedUrlService.java +++ b/src/main/java/com/swyp/app/global/infra/s3/service/S3PresignedUrlService.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.stream.Collectors; -// TODO: S3 Presigned URL 정식 구현 시 교체 필요 (임시 구현) @Service @RequiredArgsConstructor public class S3PresignedUrlService { diff --git a/src/test/java/com/swyp/app/domain/user/service/CreditServiceTest.java b/src/test/java/com/swyp/app/domain/user/service/CreditServiceTest.java index a82f810..2a53ae9 100644 --- a/src/test/java/com/swyp/app/domain/user/service/CreditServiceTest.java +++ b/src/test/java/com/swyp/app/domain/user/service/CreditServiceTest.java @@ -5,6 +5,7 @@ import com.swyp.app.domain.user.entity.User; import com.swyp.app.domain.user.enums.CreditType; import com.swyp.app.domain.user.repository.CreditHistoryRepository; +import com.swyp.app.domain.user.repository.UserRepository; import com.swyp.app.global.common.exception.CustomException; import com.swyp.app.global.common.exception.ErrorCode; import org.junit.jupiter.api.DisplayName; @@ -16,6 +17,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.dao.DataIntegrityViolationException; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -30,6 +33,9 @@ class CreditServiceTest { @Mock private CreditHistoryRepository creditHistoryRepository; + @Mock + private UserRepository userRepository; + @Mock private UserService userService; @@ -42,6 +48,7 @@ void addCredit_forCurrentUser_savesDefaultAmount() { User user = org.mockito.Mockito.mock(User.class); when(user.getId()).thenReturn(1L); when(userService.findCurrentUser()).thenReturn(user); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); creditService.addCredit(CreditType.BATTLE_VOTE, 10L); @@ -49,7 +56,7 @@ void addCredit_forCurrentUser_savesDefaultAmount() { verify(creditHistoryRepository).saveAndFlush(captor.capture()); CreditHistory saved = captor.getValue(); - assertThat(saved.getUserId()).isEqualTo(1L); + assertThat(saved.getUser().getId()).isEqualTo(1L); assertThat(saved.getCreditType()).isEqualTo(CreditType.BATTLE_VOTE); assertThat(saved.getAmount()).isEqualTo(CreditType.BATTLE_VOTE.getDefaultAmount()); assertThat(saved.getReferenceId()).isEqualTo(10L); @@ -69,6 +76,8 @@ void addCredit_withoutReferenceId_throwsException() { @Test @DisplayName("중복 적립 충돌이면 조용히 무시한다") void addCredit_duplicateInsert_ignoresConflict() { + User user = org.mockito.Mockito.mock(User.class); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); when(creditHistoryRepository.saveAndFlush(any(CreditHistory.class))) .thenThrow(new DataIntegrityViolationException("duplicate")); when(creditHistoryRepository.existsByUserIdAndCreditTypeAndReferenceId(1L, CreditType.BATTLE_VOTE, 10L)) @@ -82,6 +91,8 @@ void addCredit_duplicateInsert_ignoresConflict() { @Test @DisplayName("중복이 아닌 데이터 무결성 오류는 그대로 던진다") void addCredit_nonDuplicateIntegrityFailure_rethrows() { + User user = org.mockito.Mockito.mock(User.class); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); when(creditHistoryRepository.saveAndFlush(any(CreditHistory.class))) .thenThrow(new DataIntegrityViolationException("broken")); when(creditHistoryRepository.existsByUserIdAndCreditTypeAndReferenceId(1L, CreditType.BATTLE_VOTE, 10L)) diff --git a/src/test/java/com/swyp/app/domain/user/service/MypageServiceTest.java b/src/test/java/com/swyp/app/domain/user/service/MypageServiceTest.java index 5ce1473..08908ac 100644 --- a/src/test/java/com/swyp/app/domain/user/service/MypageServiceTest.java +++ b/src/test/java/com/swyp/app/domain/user/service/MypageServiceTest.java @@ -166,7 +166,7 @@ void getBattleRecords_returns_paginated_records() { Battle battle = createBattle("배틀 제목"); BattleOption optionA = createOption(battle, BattleOptionLabel.A); Vote vote = Vote.builder() - .userId(1L) + .user(user) .battle(battle) .preVoteOption(optionA) .build(); @@ -192,7 +192,7 @@ void getBattleRecords_returns_no_next_when_last_page() { Battle battle = createBattle("제목"); BattleOption optionA = createOption(battle, BattleOptionLabel.A); Vote vote = Vote.builder() - .userId(1L) + .user(user) .battle(battle) .preVoteOption(optionA) .build(); @@ -227,29 +227,27 @@ void getBattleRecords_applies_vote_side_filter() { @DisplayName("COMMENT 타입으로 댓글활동을 반환한다") void getContentActivities_returns_comments() { User user = createUser(1L, "tag"); - Long battleId = generateId(); - Long optionId = generateId(); + Battle battle = createBattle("배틀"); + Long battleId = battle.getId(); + BattleOption option = createOption(battle, BattleOptionLabel.A); + Long optionId = option.getId(); + Perspective perspective = Perspective.builder() - .battleId(battleId) - .userId(1L) - .optionId(optionId) + .battle(battle) + .user(user) + .option(option) .content("관점 내용") .build(); ReflectionTestUtils.setField(perspective, "id", generateId()); PerspectiveComment comment = PerspectiveComment.builder() .perspective(perspective) - .userId(1L) + .user(user) .content("댓글") .build(); ReflectionTestUtils.setField(comment, "id", generateId()); ReflectionTestUtils.setField(comment, "createdAt", LocalDateTime.now()); - Battle battle = createBattle("배틀"); - ReflectionTestUtils.setField(battle, "id", battleId); - BattleOption option = createOption(battle, BattleOptionLabel.A); - ReflectionTestUtils.setField(option, "id", optionId); - when(userService.findCurrentUser()).thenReturn(user); when(perspectiveQueryService.findUserComments(1L, 0, 20)).thenReturn(List.of(comment)); when(perspectiveQueryService.countUserComments(1L)).thenReturn(1L); @@ -268,28 +266,26 @@ void getContentActivities_returns_comments() { @DisplayName("LIKE 타입으로 좋아요활동을 반환한다") void getContentActivities_returns_likes() { User user = createUser(1L, "tag"); - Long battleId = generateId(); - Long optionId = generateId(); + Battle battle = createBattle("배틀"); + Long battleId = battle.getId(); + BattleOption option = createOption(battle, BattleOptionLabel.B); + Long optionId = option.getId(); + Perspective perspective = Perspective.builder() - .battleId(battleId) - .userId(1L) - .optionId(optionId) + .battle(battle) + .user(user) + .option(option) .content("관점 내용") .build(); ReflectionTestUtils.setField(perspective, "id", generateId()); PerspectiveLike like = PerspectiveLike.builder() .perspective(perspective) - .userId(1L) + .user(user) .build(); ReflectionTestUtils.setField(like, "id", generateId()); ReflectionTestUtils.setField(like, "createdAt", LocalDateTime.now()); - Battle battle = createBattle("배틀"); - ReflectionTestUtils.setField(battle, "id", battleId); - BattleOption option = createOption(battle, BattleOptionLabel.B); - ReflectionTestUtils.setField(option, "id", optionId); - when(userService.findCurrentUser()).thenReturn(user); when(perspectiveQueryService.findUserLikes(1L, 0, 20)).thenReturn(List.of(like)); when(perspectiveQueryService.countUserLikes(1L)).thenReturn(1L);