diff --git a/backend/src/main/java/com/ai/lawyer/domain/chatbot/controller/ChatBotController.java b/backend/src/main/java/com/ai/lawyer/domain/chatbot/controller/ChatBotController.java index 0a4a6d6..61cd792 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/chatbot/controller/ChatBotController.java +++ b/backend/src/main/java/com/ai/lawyer/domain/chatbot/controller/ChatBotController.java @@ -3,12 +3,12 @@ import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatRequest; import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatResponse; import com.ai.lawyer.domain.chatbot.service.ChatBotService; +import com.ai.lawyer.global.util.AuthUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -27,15 +27,19 @@ public class ChatBotController { @Operation(summary = "01. 새로운 채팅", description = "첫 메시지 전송으로 새로운 채팅방을 생성하고 챗봇과 대화를 시작") @PostMapping("/message") - public ResponseEntity> postNewMessage( - @AuthenticationPrincipal Long memberId, - @RequestBody ChatRequest chatRequest) { + public ResponseEntity> postNewMessage(@RequestBody ChatRequest chatRequest) { + Long memberId = AuthUtil.getAuthenticatedMemberId(); + log.debug("새로운 채팅 요청: memberId={}", memberId); return ResponseEntity.ok(chatBotService.sendMessage(memberId, chatRequest, null)); } @Operation(summary = "02. 기존 채팅", description = "기존 채팅방에 메시지를 보내고 챗봇과 대화를 이어감") @PostMapping("{roomId}/message") - public ResponseEntity> postMessage(@AuthenticationPrincipal Long memberId, @RequestBody ChatRequest chatRequest, @PathVariable(value = "roomId", required = false) Long roomId) { + public ResponseEntity> postMessage( + @RequestBody ChatRequest chatRequest, + @PathVariable(value = "roomId", required = false) Long roomId) { + Long memberId = AuthUtil.getAuthenticatedMemberId(); + log.debug("기존 채팅 요청: memberId={}, roomId={}", memberId, roomId); return ResponseEntity.ok(chatBotService.sendMessage(memberId, chatRequest, roomId)); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/entity/PollVote.java b/backend/src/main/java/com/ai/lawyer/domain/poll/entity/PollVote.java index 82bd620..4b52b93 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/entity/PollVote.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/entity/PollVote.java @@ -24,9 +24,8 @@ public class PollVote { // Member와 OAuth2Member 모두 지원하기 위해 FK 제약 조건 제거 // 애플리케이션 레벨에서 AuthUtil로 참조 무결성 보장 // foreignKey 제약조건 비활성화 (ConstraintMode.NO_CONSTRAINT) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - private Member member; + @Column(name = "member_id", nullable = false) + private Long memberId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "poll_items_id", nullable = false, foreignKey = @ForeignKey(name = "FK_POLLVOTE_POLLOPTIONS")) diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java b/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java index 1f07ca4..4a89320 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java @@ -12,21 +12,20 @@ import java.util.Optional; public interface PollVoteRepository extends JpaRepository, PollVoteRepositoryCustom { - Optional findByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId); - void deleteByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId); - List findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId); - List findByMember_MemberId(Long memberId); + Optional findByMemberIdAndPoll_PollId(Long memberId, Long pollId); + void deleteByMemberIdAndPoll_PollId(Long memberId, Long pollId); + List findByMemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId); + List findByMemberId(Long memberId); /** * member_id로 투표 내역 삭제 (회원 탈퇴 시 사용) * Member와 OAuth2Member 모두 같은 member_id 공간을 사용하므로 Long 타입으로 삭제 */ @Modifying - @Query("DELETE FROM PollVote pv WHERE pv.member.memberId = :memberId") + @Query("DELETE FROM PollVote pv WHERE pv.memberId = :memberId") void deleteByMemberIdValue(@Param("memberId") Long memberId); - boolean existsByPollAndMember(Poll poll, Member member); - - @Query("SELECT v.member.memberId FROM PollVote v WHERE v.poll = :poll") + boolean existsByPollAndMemberId(Poll poll, Long memberId); + @Query("SELECT v.memberId FROM PollVote v WHERE v.poll = :poll") List findMemberIdsByPoll(@Param("poll") Poll poll); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepositoryImpl.java b/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepositoryImpl.java index b241faf..0a2f79a 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepositoryImpl.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepositoryImpl.java @@ -94,7 +94,7 @@ public List countStaticsByPollOptionIds(List pollOptionIds pollVote.count()) .from(pollVote) .join(pollVote.getPollOptions(), pollOptions) - .join(pollVote.getMember(), member) + .join(member).on(pollVote.getMemberId().eq(member.getMemberId())) .where(pollOptions.getPollItemsId().in(pollOptionIds)) .groupBy(pollOptions.getPollItemsId(), member.getGender(), member.getAge()) .fetch(); @@ -147,7 +147,7 @@ public List getOptionAgeStatics(Long pollId) { pollVote.count()) .from(pollVote) .join(pollVote.getPollOptions(), pollOptions) - .join(pollVote.getMember(), member) + .join(member).on(pollVote.getMemberId().eq(member.getMemberId())) .where(pollOptions.getPoll().getPollId().eq(pollId)) .groupBy(pollOptions.getOption(), new com.querydsl.core.types.dsl.CaseBuilder() @@ -177,7 +177,7 @@ public List getOptionGenderStatics(Long pollId) { pollVote.count()) .from(pollVote) .join(pollVote.getPollOptions(), pollOptions) - .join(pollVote.getMember(), member) + .join(member).on(pollVote.getMemberId().eq(member.getMemberId())) .where(pollOptions.getPoll().getPollId().eq(pollId)) .groupBy(pollOptions.getOption(), member.getGender()) .fetch(); diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java b/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java index 7965743..4d46fd2 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java @@ -112,17 +112,17 @@ public PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "투표 권한이 없습니다."); } // 기존 투표 내역 조회 - var existingVoteOpt = pollVoteRepository.findByMember_MemberIdAndPoll_PollId(memberId, pollId); + var existingVoteOpt = pollVoteRepository.findByMemberIdAndPoll_PollId(memberId, pollId); if (existingVoteOpt.isPresent()) { PollVote existingVote = existingVoteOpt.get(); if (existingVote.getPollOptions().getPollItemsId().equals(pollItemsId)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미 투표하셨습니다."); } else { - pollVoteRepository.deleteByMember_MemberIdAndPoll_PollId(memberId, pollId); + pollVoteRepository.deleteByMemberIdAndPoll_PollId(memberId, pollId); PollVote pollVote = PollVote.builder() .poll(poll) .pollOptions(pollOptions) - .member(member) + .memberId(memberId) .build(); PollVote savedVote = pollVoteRepository.save(pollVote); Long voteCount = pollVoteRepository.countByPollOptionId(pollItemsId); @@ -140,7 +140,7 @@ public PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId) { PollVote pollVote = PollVote.builder() .poll(poll) .pollOptions(pollOptions) - .member(member) + .memberId(memberId) .build(); PollVote savedVote = pollVoteRepository.save(pollVote); Long voteCount = pollVoteRepository.countByPollOptionId(pollItemsId); @@ -441,7 +441,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) { Long voteCount = pollVoteRepository.countByPollOptionId(option.getPollItemsId()); boolean voted = false; if (memberId != null) { - voted = !pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isEmpty(); + voted = !pollVoteRepository.findByMemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isEmpty(); } List statics = null; if (withStatistics && poll.getStatus() == Poll.PollStatus.CLOSED) { @@ -539,7 +539,7 @@ public void validatePollCreate(PollCreateDto dto) { @Override public void cancelVote(Long pollId, Long memberId) { - pollVoteRepository.findByMember_MemberIdAndPoll_PollId(memberId, pollId) + pollVoteRepository.findByMemberIdAndPoll_PollId(memberId, pollId) .ifPresent(pollVoteRepository::delete); } } diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostDummyController.java b/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostDummyController.java index 170c485..7e1a1b2 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostDummyController.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostDummyController.java @@ -38,4 +38,20 @@ public ResponseEntity deleteDummyMembers() { int deleted = dummyService.deleteDummyMembers(); return ResponseEntity.ok("더미 멤버 " + deleted + "명 삭제 완료"); } + + //모든 더미 유저가 1번 옵션에 투표 + @Operation(summary = "더미 멤버 1번 투표") + @PostMapping("/vote1") + public ResponseEntity dummyVote1Option(@RequestParam Long postId) { + int voteCount = dummyService.dummyVote1Option(postId); + return ResponseEntity.ok("더미 멤버 " + voteCount + "명 투표 완료"); + } + + //모든 더미 유저가 2번 옵션에 투표 + @Operation(summary = "더미 멤버 2번 투표") + @PostMapping("/vote2") + public ResponseEntity dummyVote2Option(@RequestParam Long postId) { + int voteCount = dummyService.dummyVote2Option(postId); + return ResponseEntity.ok("더미 멤버 " + voteCount + "명 투표 완료"); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostDummyService.java b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostDummyService.java index c0348ec..9e83c5e 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostDummyService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostDummyService.java @@ -104,7 +104,7 @@ public int dummyVote(Long postId) { PollOptions selectedOption = pollOptionsList.get(random.nextInt(pollOptionsList.size())); PollVote pollVote = PollVote.builder() .poll(post.getPoll()) - .member(member) + .memberId(member.getMemberId()) .pollOptions(selectedOption) .build(); pollVoteRepository.save(pollVote); @@ -114,6 +114,70 @@ public int dummyVote(Long postId) { return voteCount; } + /** + * 모든 더미 유저가 1번 옵션에 투표 + */ + @Transactional + public int dummyVote1Option(Long postId) { + Optional postOpt = postRepository.findById(postId); + if (postOpt.isEmpty()) return 0; + Post post = postOpt.get(); + if (post.getPoll() == null) return 0; + List pollOptionsList = pollOptionsRepository.findByPoll_PollId(post.getPoll().getPollId()); + if (pollOptionsList.size() < 1) return 0; + PollOptions firstOption = pollOptionsList.get(0); + List dummyMembers = memberRepository.findAll().stream() + .filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com")) + .toList(); + List votedMemberIds = pollVoteRepository.findMemberIdsByPoll(post.getPoll()); + Set votedMemberIdSet = new HashSet<>(votedMemberIds); + int voteCount = 0; + for (Member member : dummyMembers) { + if (!votedMemberIdSet.contains(member.getMemberId())) { + PollVote pollVote = PollVote.builder() + .poll(post.getPoll()) + .memberId(member.getMemberId()) + .pollOptions(firstOption) + .build(); + pollVoteRepository.save(pollVote); + voteCount++; + } + } + return voteCount; + } + + /** + * 모든 더미 유저가 2번 옵션에 투표 + */ + @Transactional + public int dummyVote2Option(Long postId) { + Optional postOpt = postRepository.findById(postId); + if (postOpt.isEmpty()) return 0; + Post post = postOpt.get(); + if (post.getPoll() == null) return 0; + List pollOptionsList = pollOptionsRepository.findByPoll_PollId(post.getPoll().getPollId()); + if (pollOptionsList.size() < 2) return 0; + PollOptions secondOption = pollOptionsList.get(1); + List dummyMembers = memberRepository.findAll().stream() + .filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com")) + .toList(); + List votedMemberIds = pollVoteRepository.findMemberIdsByPoll(post.getPoll()); + Set votedMemberIdSet = new HashSet<>(votedMemberIds); + int voteCount = 0; + for (Member member : dummyMembers) { + if (!votedMemberIdSet.contains(member.getMemberId())) { + PollVote pollVote = PollVote.builder() + .poll(post.getPoll()) + .memberId(member.getMemberId()) + .pollOptions(secondOption) + .build(); + pollVoteRepository.save(pollVote); + voteCount++; + } + } + return voteCount; + } + @Transactional public int deleteDummyMembers() { List dummyMembers = memberRepository.findAll().stream() diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java index 5aaa32c..7c0dc5e 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java @@ -300,7 +300,7 @@ public Page getClosedPostsPaged(Pageable pageable, Long memberId) { } private Page getMyVotedPostsPagedByStatus(Pageable pageable, Long memberId, Poll.PollStatus status) { - List votes = pollVoteRepository.findByMember_MemberId(memberId); + List votes = pollVoteRepository.findByMemberId(memberId); List pollIds = votes.stream().map(v -> v.getPoll().getPollId()).distinct().toList(); Page posts = (status == null) ? postRepository.findByPoll_PollIdIn(pollIds, pageable)