Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
Expand All @@ -12,4 +13,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByLoginId(String loginId);

boolean existsByLoginId(String loginId);

List<Member> findByLoginIdIn(List<String> loginIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ public ResponseEntity<ApiResponse<PollVoteDto>> voteByIndex(@PathVariable Long p
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 성공적으로 완료되었습니다.", result));
}

@Operation(summary = "투표 취소하기")
@DeleteMapping("/vote")
public ResponseEntity<ApiResponse<Void>> cancelVote(@RequestParam Long pollId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long memberId = Long.parseLong(authentication.getName());
pollService.cancelVote(pollId, memberId);
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 취소되었습니다.", null));
}

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ApiResponse<Void>> handleResponseStatusException(ResponseStatusException ex) {
int code = ex.getStatusCode().value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public class PollStaticsResponseDto {
private Long pollId;
private List<PollAgeStaticsDto> optionAgeStatics;
private List<PollGenderStaticsDto> optionGenderStatics;
private Long totalVoteCount;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ai.lawyer.domain.poll.repository;

import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.entity.Poll;
import com.ai.lawyer.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -12,7 +14,7 @@
public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollVoteRepositoryCustom {
Optional<PollVote> findByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
void deleteByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
Optional<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
List<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
List<PollVote> findByMember_MemberId(Long memberId);

/**
Expand All @@ -22,4 +24,9 @@ public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollV
@Modifying
@Query("DELETE FROM PollVote pv WHERE pv.member.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")
List<Long> findMemberIdsByPoll(@Param("poll") Poll poll);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public interface PollService {
// ===== 투표 관련 =====
PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId);

// ===== 투표 취소 관련 =====
void cancelVote(Long pollId, Long memberId);

// ===== 생성/수정/삭제 관련 =====
PollDto createPoll(PollCreateDto request, Long memberId);
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.ArrayList;
import java.util.*;
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
import com.ai.lawyer.domain.poll.dto.PollForPostDto;
import com.ai.lawyer.domain.poll.dto.PollOptionCreateDto;
Expand Down Expand Up @@ -227,11 +226,13 @@ public PollStaticsResponseDto getPollStatics(Long pollId) {
.genderCounts(genderGroupMap.getOrDefault(opt.getPollItemsId(), java.util.Collections.emptyList()))
.build());
}
Long totalVoteCount = pollVoteRepository.countByPollId(pollId);
return PollStaticsResponseDto.builder()
.postId(postId)
.pollId(pollId)
.optionAgeStatics(optionAgeStatics)
.optionGenderStatics(optionGenderStatics)
.totalVoteCount(totalVoteCount)
.build();
}

Expand Down Expand Up @@ -450,7 +451,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()).isPresent();
voted = !pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isEmpty();
}
List<PollStaticsDto> statics = null;
if (withStatistics && poll.getStatus() == Poll.PollStatus.CLOSED) {
Expand Down Expand Up @@ -485,6 +486,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
.createdAt(poll.getCreatedAt())
.closedAt(poll.getClosedAt())
.expectedCloseAt(expectedCloseAt)
.pollOptions(optionDtos)
.totalVoteCount(totalVoteCount)
.build();
}
Expand Down Expand Up @@ -554,4 +556,10 @@ public void validatePollCreate(PollForPostDto dto) {
public void validatePollCreate(PollCreateDto dto) {
validatePollCommon(dto.getVoteTitle(), dto.getPollOptions(), dto.getReservedCloseAt());
}

@Override
public void cancelVote(Long pollId, Long memberId) {
pollVoteRepository.findByMember_MemberIdAndPoll_PollId(memberId, pollId)
.ifPresent(pollVoteRepository::delete);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.ai.lawyer.domain.post.controller;

import com.ai.lawyer.domain.post.service.PostDummyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;

@Tag(name = "더미생성")
@RestController
@RequestMapping("/api/dummy")
public class PostDummyController {
private final PostDummyService dummyService;

@Autowired
public PostDummyController(PostDummyService dummyService) {
this.dummyService = dummyService;
}

@Operation(summary = "더미 멤버 추가")
@PostMapping("/members")
public ResponseEntity<String> createDummyMembers(@RequestParam(defaultValue = "100") int count) {
int created = dummyService.createDummyMembers(count);
return ResponseEntity.ok("더미 멤버 " + created + "명 생성 완료");
}

@Operation(summary = "더미 멤버 투표")
@PostMapping("/vote")
public ResponseEntity<String> dummyVote(@RequestParam Long postId) {
int voteCount = dummyService.dummyVote(postId);
return ResponseEntity.ok("더미 멤버 " + voteCount + "명 투표 완료");
}

@Operation(summary = "더미 멤버 삭제")
@DeleteMapping("/members")
public ResponseEntity<String> deleteDummyMembers() {
int deleted = dummyService.deleteDummyMembers();
return ResponseEntity.ok("더미 멤버 " + deleted + "명 삭제 완료");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findByPoll_Status(PollStatus status, Pageable pageable);
Page<Post> findByPoll_StatusAndPoll_PollIdIn(PollStatus status, List<Long> pollIds, Pageable pageable);
Page<Post> findByPoll_PollIdIn(List<Long> pollIds, Pageable pageable);
boolean existsByPostName(String postName);
List<Post> findByPostName(String postName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.ai.lawyer.domain.post.service;

import com.ai.lawyer.domain.member.entity.Member;
import com.ai.lawyer.domain.member.entity.Member.Gender;
import com.ai.lawyer.domain.member.repositories.MemberRepository;
import com.ai.lawyer.domain.poll.entity.PollOptions;
import com.ai.lawyer.domain.poll.entity.PollVote;
import com.ai.lawyer.domain.poll.repository.PollOptionsRepository;
import com.ai.lawyer.domain.poll.repository.PollVoteRepository;
import com.ai.lawyer.domain.post.entity.Post;
import com.ai.lawyer.domain.post.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class PostDummyService {
private final MemberRepository memberRepository;
private final PostRepository postRepository;
private final PollOptionsRepository pollOptionsRepository;
private final PollVoteRepository pollVoteRepository;

@Autowired
public PostDummyService(MemberRepository memberRepository,
PostRepository postRepository,
PollOptionsRepository pollOptionsRepository,
PollVoteRepository pollVoteRepository) {
this.memberRepository = memberRepository;
this.postRepository = postRepository;
this.pollOptionsRepository = pollOptionsRepository;
this.pollVoteRepository = pollVoteRepository;
}

@Transactional
public int createDummyMembers(int count) {
List<Member> allMembers = memberRepository.findAll();
int maxDummyNumber = allMembers.stream()
.map(Member::getLoginId)
.filter(id -> id.startsWith("dummy") && id.endsWith("@test.com"))
.map(id -> {
try {
String numStr = id.substring(5, id.indexOf("@"));
return Integer.parseInt(numStr);
} catch (Exception e) {
return 0;
}
})
.max(Integer::compareTo)
.orElse(0);
int start = maxDummyNumber + 1;
int end = start + count - 1;
List<String> newLoginIds = new ArrayList<>();
for (int i = start; i <= end; i++) {
newLoginIds.add("dummy" + i + "@test.com");
}
List<Member> existingMembers = memberRepository.findByLoginIdIn(newLoginIds);
Set<String> existingLoginIds = new HashSet<>();
for (Member m : existingMembers) {
existingLoginIds.add(m.getLoginId());
}
List<Member> membersToSave = new ArrayList<>();
Random random = new Random();
for (int i = start; i <= end; i++) {
String loginId = "dummy" + i + "@test.com";
if (!existingLoginIds.contains(loginId)) {
int age = 14 + random.nextInt(67);
Gender gender = (i % 2 == 0) ? Gender.MALE : Gender.FEMALE;
Member member = Member.builder()
.loginId(loginId)
.password("password")
.age(age)
.gender(gender)
.name("투표자" + i)
.build();
membersToSave.add(member);
}
}
if (!membersToSave.isEmpty()) {
memberRepository.saveAll(membersToSave);
}
return membersToSave.size();
}

@Transactional
public int dummyVote(Long postId) {
Optional<Post> postOpt = postRepository.findById(postId);
if (postOpt.isEmpty()) return 0;
Post post = postOpt.get();
if (post.getPoll() == null) return 0;
List<PollOptions> pollOptionsList = pollOptionsRepository.findByPoll_PollId(post.getPoll().getPollId());
if (pollOptionsList.isEmpty()) return 0;
// 모든 멤버 조회 후 더미 멤버만 필터링
List<Member> dummyMembers = memberRepository.findAll().stream()
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
.toList();
List<Long> votedMemberIds = pollVoteRepository.findMemberIdsByPoll(post.getPoll());
Set<Long> votedMemberIdSet = new HashSet<>(votedMemberIds);
int voteCount = 0;
Random random = new Random();
for (Member member : dummyMembers) {
if (!votedMemberIdSet.contains(member.getMemberId())) {
PollOptions selectedOption = pollOptionsList.get(random.nextInt(pollOptionsList.size()));
PollVote pollVote = PollVote.builder()
.poll(post.getPoll())
.member(member)
.pollOptions(selectedOption)
.build();
pollVoteRepository.save(pollVote);
voteCount++;
}
}
return voteCount;
}

@Transactional
public int deleteDummyMembers() {
List<Member> dummyMembers = memberRepository.findAll().stream()
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
.toList();
int count = dummyMembers.size();
if (count > 0) {
for (Member member : dummyMembers) {
pollVoteRepository.deleteByMemberIdValue(member.getMemberId());
}
memberRepository.deleteAll(dummyMembers);
}
return count;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class SecurityConfig {
"/swagger-ui/**", // Swagger UI
"/swagger-ui.html", // Swagger UI HTML
"/api/posts/**", // 게시글 (공개)
"/api/polls/{pollId}/statics", // 투표 통계 (공개)
"/api/precedent/**", // 판례 (공개)
"/api/law/**", // 법령 (공개)
"/api/law-word/**", // 법률 용어 (공개)
Expand Down