Skip to content

Commit ebaed27

Browse files
authored
Merge pull request #278 from prgrms-web-devcourse-final-project/develop
배포
2 parents 0d04c40 + a49dfbe commit ebaed27

File tree

11 files changed

+221
-18
lines changed

11 files changed

+221
-18
lines changed

backend/src/main/java/com/ai/lawyer/domain/member/repositories/MemberRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.data.jpa.repository.JpaRepository;
55
import org.springframework.stereotype.Repository;
66

7+
import java.util.List;
78
import java.util.Optional;
89

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

1415
boolean existsByLoginId(String loginId);
16+
17+
List<Member> findByLoginIdIn(List<String> loginIds);
1518
}

backend/src/main/java/com/ai/lawyer/domain/poll/controller/PollController.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ public ResponseEntity<ApiResponse<PollVoteDto>> voteByIndex(@PathVariable Long p
164164
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 성공적으로 완료되었습니다.", result));
165165
}
166166

167+
@Operation(summary = "투표 취소하기")
168+
@DeleteMapping("/vote")
169+
public ResponseEntity<ApiResponse<Void>> cancelVote(@RequestParam Long pollId) {
170+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
171+
Long memberId = Long.parseLong(authentication.getName());
172+
pollService.cancelVote(pollId, memberId);
173+
return ResponseEntity.ok(new ApiResponse<>(200, "투표가 취소되었습니다.", null));
174+
}
175+
167176
@ExceptionHandler(ResponseStatusException.class)
168177
public ResponseEntity<ApiResponse<Void>> handleResponseStatusException(ResponseStatusException ex) {
169178
int code = ex.getStatusCode().value();

backend/src/main/java/com/ai/lawyer/domain/poll/dto/PollStaticsResponseDto.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class PollStaticsResponseDto {
1212
private Long pollId;
1313
private List<PollAgeStaticsDto> optionAgeStatics;
1414
private List<PollGenderStaticsDto> optionGenderStatics;
15+
private Long totalVoteCount;
1516
}

backend/src/main/java/com/ai/lawyer/domain/poll/repository/PollVoteRepository.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.ai.lawyer.domain.poll.repository;
22

33
import com.ai.lawyer.domain.poll.entity.PollVote;
4+
import com.ai.lawyer.domain.poll.entity.Poll;
5+
import com.ai.lawyer.domain.member.entity.Member;
46
import org.springframework.data.jpa.repository.JpaRepository;
57
import org.springframework.data.jpa.repository.Modifying;
68
import org.springframework.data.jpa.repository.Query;
@@ -12,7 +14,7 @@
1214
public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollVoteRepositoryCustom {
1315
Optional<PollVote> findByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
1416
void deleteByMember_MemberIdAndPoll_PollId(Long memberId, Long pollId);
15-
Optional<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
17+
List<PollVote> findByMember_MemberIdAndPollOptions_PollItemsId(Long memberId, Long pollItemsId);
1618
List<PollVote> findByMember_MemberId(Long memberId);
1719

1820
/**
@@ -22,4 +24,9 @@ public interface PollVoteRepository extends JpaRepository<PollVote, Long>, PollV
2224
@Modifying
2325
@Query("DELETE FROM PollVote pv WHERE pv.member.memberId = :memberId")
2426
void deleteByMemberIdValue(@Param("memberId") Long memberId);
27+
28+
boolean existsByPollAndMember(Poll poll, Member member);
29+
30+
@Query("SELECT v.member.memberId FROM PollVote v WHERE v.poll = :poll")
31+
List<Long> findMemberIdsByPoll(@Param("poll") Poll poll);
2532
}

backend/src/main/java/com/ai/lawyer/domain/poll/service/PollService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public interface PollService {
2929
// ===== 투표 관련 =====
3030
PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId);
3131

32+
// ===== 투표 취소 관련 =====
33+
void cancelVote(Long pollId, Long memberId);
34+
3235
// ===== 생성/수정/삭제 관련 =====
3336
PollDto createPoll(PollCreateDto request, Long memberId);
3437
PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberId);

backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
import org.springframework.transaction.annotation.Transactional;
1616
import org.springframework.web.server.ResponseStatusException;
1717

18-
import java.util.List;
19-
import java.util.ArrayList;
18+
import java.util.*;
2019
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
2120
import com.ai.lawyer.domain.poll.dto.PollForPostDto;
2221
import com.ai.lawyer.domain.poll.dto.PollOptionCreateDto;
@@ -227,11 +226,13 @@ public PollStaticsResponseDto getPollStatics(Long pollId) {
227226
.genderCounts(genderGroupMap.getOrDefault(opt.getPollItemsId(), java.util.Collections.emptyList()))
228227
.build());
229228
}
229+
Long totalVoteCount = pollVoteRepository.countByPollId(pollId);
230230
return PollStaticsResponseDto.builder()
231231
.postId(postId)
232232
.pollId(pollId)
233233
.optionAgeStatics(optionAgeStatics)
234234
.optionGenderStatics(optionGenderStatics)
235+
.totalVoteCount(totalVoteCount)
235236
.build();
236237
}
237238

@@ -450,7 +451,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
450451
Long voteCount = pollVoteRepository.countByPollOptionId(option.getPollItemsId());
451452
boolean voted = false;
452453
if (memberId != null) {
453-
voted = pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isPresent();
454+
voted = !pollVoteRepository.findByMember_MemberIdAndPollOptions_PollItemsId(memberId, option.getPollItemsId()).isEmpty();
454455
}
455456
List<PollStaticsDto> statics = null;
456457
if (withStatistics && poll.getStatus() == Poll.PollStatus.CLOSED) {
@@ -485,6 +486,7 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) {
485486
.createdAt(poll.getCreatedAt())
486487
.closedAt(poll.getClosedAt())
487488
.expectedCloseAt(expectedCloseAt)
489+
.pollOptions(optionDtos)
488490
.totalVoteCount(totalVoteCount)
489491
.build();
490492
}
@@ -554,4 +556,10 @@ public void validatePollCreate(PollForPostDto dto) {
554556
public void validatePollCreate(PollCreateDto dto) {
555557
validatePollCommon(dto.getVoteTitle(), dto.getPollOptions(), dto.getReservedCloseAt());
556558
}
559+
560+
@Override
561+
public void cancelVote(Long pollId, Long memberId) {
562+
pollVoteRepository.findByMember_MemberIdAndPoll_PollId(memberId, pollId)
563+
.ifPresent(pollVoteRepository::delete);
564+
}
557565
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.ai.lawyer.domain.post.controller;
2+
3+
import com.ai.lawyer.domain.post.service.PostDummyService;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import org.springframework.web.bind.annotation.*;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.http.ResponseEntity;
9+
10+
@Tag(name = "더미생성")
11+
@RestController
12+
@RequestMapping("/api/dummy")
13+
public class PostDummyController {
14+
private final PostDummyService dummyService;
15+
16+
@Autowired
17+
public PostDummyController(PostDummyService dummyService) {
18+
this.dummyService = dummyService;
19+
}
20+
21+
@Operation(summary = "더미 멤버 추가")
22+
@PostMapping("/members")
23+
public ResponseEntity<String> createDummyMembers(@RequestParam(defaultValue = "100") int count) {
24+
int created = dummyService.createDummyMembers(count);
25+
return ResponseEntity.ok("더미 멤버 " + created + "명 생성 완료");
26+
}
27+
28+
@Operation(summary = "더미 멤버 투표")
29+
@PostMapping("/vote")
30+
public ResponseEntity<String> dummyVote(@RequestParam Long postId) {
31+
int voteCount = dummyService.dummyVote(postId);
32+
return ResponseEntity.ok("더미 멤버 " + voteCount + "명 투표 완료");
33+
}
34+
35+
@Operation(summary = "더미 멤버 삭제")
36+
@DeleteMapping("/members")
37+
public ResponseEntity<String> deleteDummyMembers() {
38+
int deleted = dummyService.deleteDummyMembers();
39+
return ResponseEntity.ok("더미 멤버 " + deleted + "명 삭제 완료");
40+
}
41+
}

backend/src/main/java/com/ai/lawyer/domain/post/repository/PostRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ public interface PostRepository extends JpaRepository<Post, Long> {
2929
Page<Post> findByPoll_Status(PollStatus status, Pageable pageable);
3030
Page<Post> findByPoll_StatusAndPoll_PollIdIn(PollStatus status, List<Long> pollIds, Pageable pageable);
3131
Page<Post> findByPoll_PollIdIn(List<Long> pollIds, Pageable pageable);
32+
boolean existsByPostName(String postName);
33+
List<Post> findByPostName(String postName);
3234
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.ai.lawyer.domain.post.service;
2+
3+
import com.ai.lawyer.domain.member.entity.Member;
4+
import com.ai.lawyer.domain.member.entity.Member.Gender;
5+
import com.ai.lawyer.domain.member.repositories.MemberRepository;
6+
import com.ai.lawyer.domain.poll.entity.PollOptions;
7+
import com.ai.lawyer.domain.poll.entity.PollVote;
8+
import com.ai.lawyer.domain.poll.repository.PollOptionsRepository;
9+
import com.ai.lawyer.domain.poll.repository.PollVoteRepository;
10+
import com.ai.lawyer.domain.post.entity.Post;
11+
import com.ai.lawyer.domain.post.repository.PostRepository;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
import java.util.*;
17+
18+
@Service
19+
public class PostDummyService {
20+
private final MemberRepository memberRepository;
21+
private final PostRepository postRepository;
22+
private final PollOptionsRepository pollOptionsRepository;
23+
private final PollVoteRepository pollVoteRepository;
24+
25+
@Autowired
26+
public PostDummyService(MemberRepository memberRepository,
27+
PostRepository postRepository,
28+
PollOptionsRepository pollOptionsRepository,
29+
PollVoteRepository pollVoteRepository) {
30+
this.memberRepository = memberRepository;
31+
this.postRepository = postRepository;
32+
this.pollOptionsRepository = pollOptionsRepository;
33+
this.pollVoteRepository = pollVoteRepository;
34+
}
35+
36+
@Transactional
37+
public int createDummyMembers(int count) {
38+
List<Member> allMembers = memberRepository.findAll();
39+
int maxDummyNumber = allMembers.stream()
40+
.map(Member::getLoginId)
41+
.filter(id -> id.startsWith("dummy") && id.endsWith("@test.com"))
42+
.map(id -> {
43+
try {
44+
String numStr = id.substring(5, id.indexOf("@"));
45+
return Integer.parseInt(numStr);
46+
} catch (Exception e) {
47+
return 0;
48+
}
49+
})
50+
.max(Integer::compareTo)
51+
.orElse(0);
52+
int start = maxDummyNumber + 1;
53+
int end = start + count - 1;
54+
List<String> newLoginIds = new ArrayList<>();
55+
for (int i = start; i <= end; i++) {
56+
newLoginIds.add("dummy" + i + "@test.com");
57+
}
58+
List<Member> existingMembers = memberRepository.findByLoginIdIn(newLoginIds);
59+
Set<String> existingLoginIds = new HashSet<>();
60+
for (Member m : existingMembers) {
61+
existingLoginIds.add(m.getLoginId());
62+
}
63+
List<Member> membersToSave = new ArrayList<>();
64+
Random random = new Random();
65+
for (int i = start; i <= end; i++) {
66+
String loginId = "dummy" + i + "@test.com";
67+
if (!existingLoginIds.contains(loginId)) {
68+
int age = 14 + random.nextInt(67);
69+
Gender gender = (i % 2 == 0) ? Gender.MALE : Gender.FEMALE;
70+
Member member = Member.builder()
71+
.loginId(loginId)
72+
.password("password")
73+
.age(age)
74+
.gender(gender)
75+
.name("투표자" + i)
76+
.build();
77+
membersToSave.add(member);
78+
}
79+
}
80+
if (!membersToSave.isEmpty()) {
81+
memberRepository.saveAll(membersToSave);
82+
}
83+
return membersToSave.size();
84+
}
85+
86+
@Transactional
87+
public int dummyVote(Long postId) {
88+
Optional<Post> postOpt = postRepository.findById(postId);
89+
if (postOpt.isEmpty()) return 0;
90+
Post post = postOpt.get();
91+
if (post.getPoll() == null) return 0;
92+
List<PollOptions> pollOptionsList = pollOptionsRepository.findByPoll_PollId(post.getPoll().getPollId());
93+
if (pollOptionsList.isEmpty()) return 0;
94+
// 모든 멤버 조회 후 더미 멤버만 필터링
95+
List<Member> dummyMembers = memberRepository.findAll().stream()
96+
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
97+
.toList();
98+
List<Long> votedMemberIds = pollVoteRepository.findMemberIdsByPoll(post.getPoll());
99+
Set<Long> votedMemberIdSet = new HashSet<>(votedMemberIds);
100+
int voteCount = 0;
101+
Random random = new Random();
102+
for (Member member : dummyMembers) {
103+
if (!votedMemberIdSet.contains(member.getMemberId())) {
104+
PollOptions selectedOption = pollOptionsList.get(random.nextInt(pollOptionsList.size()));
105+
PollVote pollVote = PollVote.builder()
106+
.poll(post.getPoll())
107+
.member(member)
108+
.pollOptions(selectedOption)
109+
.build();
110+
pollVoteRepository.save(pollVote);
111+
voteCount++;
112+
}
113+
}
114+
return voteCount;
115+
}
116+
117+
@Transactional
118+
public int deleteDummyMembers() {
119+
List<Member> dummyMembers = memberRepository.findAll().stream()
120+
.filter(m -> m.getLoginId().startsWith("dummy") && m.getLoginId().endsWith("@test.com"))
121+
.toList();
122+
int count = dummyMembers.size();
123+
if (count > 0) {
124+
for (Member member : dummyMembers) {
125+
pollVoteRepository.deleteByMemberIdValue(member.getMemberId());
126+
}
127+
memberRepository.deleteAll(dummyMembers);
128+
}
129+
return count;
130+
}
131+
}

backend/src/main/java/com/ai/lawyer/global/security/SecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class SecurityConfig {
5252
"/swagger-ui/**", // Swagger UI
5353
"/swagger-ui.html", // Swagger UI HTML
5454
"/api/posts/**", // 게시글 (공개)
55+
"/api/polls/{pollId}/statics", // 투표 통계 (공개)
5556
"/api/precedent/**", // 판례 (공개)
5657
"/api/law/**", // 법령 (공개)
5758
"/api/law-word/**", // 법률 용어 (공개)

0 commit comments

Comments
 (0)