22
33import com .ai .lawyer .domain .poll .entity .*;
44import com .ai .lawyer .domain .poll .repository .*;
5- import com .ai .lawyer .domain .poll .service .*;
65import com .ai .lawyer .domain .poll .dto .PollDto ;
76import com .ai .lawyer .domain .member .entity .Member ;
87import com .ai .lawyer .domain .member .repositories .MemberRepository ;
1716import java .util .List ;
1817import java .util .ArrayList ;
1918import com .ai .lawyer .domain .poll .dto .PollCreateDto ;
19+ import com .ai .lawyer .domain .poll .dto .PollForPostDto ;
2020import com .ai .lawyer .domain .poll .dto .PollOptionCreateDto ;
2121import com .ai .lawyer .domain .poll .dto .PollStaticsDto ;
2222import com .ai .lawyer .domain .poll .dto .PollOptionDto ;
2727import org .springframework .data .domain .Pageable ;
2828import java .time .LocalDateTime ;
2929
30+ import com .ai .lawyer .domain .poll .dto .PollGenderStaticsDto ;
31+ import com .ai .lawyer .domain .poll .dto .PollStaticsResponseDto ;
32+ import com .ai .lawyer .domain .poll .dto .PollAgeStaticsDto ;
33+
3034@ Service
3135@ Transactional
3236@ RequiredArgsConstructor
@@ -42,44 +46,24 @@ public class PollServiceImpl implements PollService {
4246 @ Override
4347 public PollDto createPoll (PollCreateDto request , Long memberId ) {
4448 if (request .getPostId () == null ) {
45- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "게시글 ID(postId)는 필수입니다." );
46- }
47- if (request .getVoteTitle () == null || request .getVoteTitle ().trim ().isEmpty ()) {
48- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "투표 제목(voteTitle)은 필수입니다." );
49- }
50- if (request .getPollOptions () == null || request .getPollOptions ().size () != 2 ) {
51- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "투표 항목은 2개여야 합니다." );
52- }
53- for (PollOptionCreateDto option : request .getPollOptions ()) {
54- if (option .getContent () == null || option .getContent ().trim ().isEmpty ()) {
55- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "각 투표 항목의 내용(content)은 필수입니다." );
56- }
49+ throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "게시글 ID는 필수입니다." );
5750 }
51+ validatePollCommon (request .getVoteTitle (), request .getPollOptions (), request .getReservedCloseAt ());
5852 Member member = memberRepository .findById (memberId )
5953 .orElseThrow (() -> new ResponseStatusException (HttpStatus .NOT_FOUND , "회원 정보를 찾을 수 없습니다." ));
6054 Post post = postRepository .findById (request .getPostId ())
6155 .orElseThrow (() -> new ResponseStatusException (HttpStatus .NOT_FOUND , "게시글을 찾을 수 없습니다." ));
62- // 이미 해당 게시글에 투표가 존재하는 경우 예외 처리
6356 if (post .getPoll () != null ) {
6457 throw new ResponseStatusException (HttpStatus .CONFLICT , "이미 해당 게시글에 투표가 존재합니다." );
6558 }
6659 try {
6760 LocalDateTime now = java .time .LocalDateTime .now ();
68- LocalDateTime reservedCloseAt = request .getReservedCloseAt ();
69- if (reservedCloseAt != null ) {
70- if (reservedCloseAt .isBefore (now .plusHours (1 ))) {
71- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "예약 종료 시간은 현재로부터 최소 1시간 이후여야 합니다." );
72- }
73- if (reservedCloseAt .isAfter (now .plusDays (7 ))) {
74- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "예약 종료 시간은 최대 7일 이내여야 합니다." );
75- }
76- }
7761 Poll poll = Poll .builder ()
7862 .post (post )
7963 .voteTitle (request .getVoteTitle ())
8064 .status (Poll .PollStatus .ONGOING )
8165 .createdAt (now )
82- .reservedCloseAt (reservedCloseAt )
66+ .reservedCloseAt (request . getReservedCloseAt () )
8367 .build ();
8468 Poll savedPoll = pollRepository .save (poll );
8569 post .setPoll (savedPoll );
@@ -119,7 +103,9 @@ public List<PollDto> getPollsByStatus(PollDto.PollStatus status) {
119103 }
120104 List <PollDto > pollDtos = polls .stream ()
121105 .filter (p -> p .getStatus ().name ().equals (status .name ()))
122- .map (this ::convertToDto )
106+ .map (p -> status == PollDto .PollStatus .CLOSED
107+ ? getPollWithStatistics (p .getPollId ())
108+ : convertToDto (p ))
123109 .toList ();
124110 return pollDtos ;
125111 }
@@ -163,12 +149,72 @@ public PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId) {
163149 }
164150
165151 @ Override
166- public List <PollStatics > getPollStatics (Long pollId ) {
167- // 투표 존재 여부 체크
152+ public PollStaticsResponseDto getPollStatics (Long pollId ) {
168153 if (!pollRepository .existsById (pollId )) {
169154 throw new ResponseStatusException (HttpStatus .NOT_FOUND , "해당 투표가 존재하지 않습니다." );
170155 }
171- return pollStaticsRepository .findByPoll_PollId (pollId );
156+ Poll poll = pollRepository .findById (pollId ).orElseThrow (() -> new ResponseStatusException (HttpStatus .NOT_FOUND , "투표를 찾을 수 없습니다." ));
157+ Long postId = poll .getPost () != null ? poll .getPost ().getPostId () : null ;
158+ List <PollOptions > options = pollOptionsRepository .findByPoll_PollId (pollId );
159+ java .util .Map <String , PollOptions > optionMap = new java .util .HashMap <>();
160+ for (int i = 0 ; i < options .size (); i ++) {
161+ PollOptions opt = options .get (i );
162+ optionMap .put (opt .getOption (), opt );
163+ }
164+ // age 통계 그룹핑
165+ List <Object []> optionAgeRaw = pollVoteRepository .getOptionAgeStatics (pollId );
166+ java .util .Map <Long , java .util .List <PollAgeStaticsDto .AgeGroupCountDto >> ageGroupMap = new java .util .HashMap <>();
167+ for (Object [] arr : optionAgeRaw ) {
168+ String option = arr [0 ] != null ? arr [0 ].toString () : null ;
169+ PollOptions opt = optionMap .get (option );
170+ if (opt == null ) continue ;
171+ Long pollItemsId = opt .getPollItemsId ();
172+ PollAgeStaticsDto .AgeGroupCountDto dto = PollAgeStaticsDto .AgeGroupCountDto .builder ()
173+ .option (option )
174+ .ageGroup (arr [1 ] != null ? arr [1 ].toString () : null )
175+ .voteCount (arr [2 ] != null ? ((Number )arr [2 ]).longValue () : 0L )
176+ .build ();
177+ ageGroupMap .computeIfAbsent (pollItemsId , k -> new java .util .ArrayList <>()).add (dto );
178+ }
179+ java .util .List <PollAgeStaticsDto > optionAgeStatics = new java .util .ArrayList <>();
180+ for (int i = 0 ; i < options .size (); i ++) {
181+ PollOptions opt = options .get (i );
182+ optionAgeStatics .add (PollAgeStaticsDto .builder ()
183+ .pollItemsId (opt .getPollItemsId ())
184+ .pollOptionIndex (i + 1 )
185+ .ageGroupCounts (ageGroupMap .getOrDefault (opt .getPollItemsId (), java .util .Collections .emptyList ()))
186+ .build ());
187+ }
188+ // gender 통계 그룹핑
189+ List <Object []> optionGenderRaw = pollVoteRepository .getOptionGenderStatics (pollId );
190+ java .util .Map <Long , java .util .List <PollGenderStaticsDto .GenderCountDto >> genderGroupMap = new java .util .HashMap <>();
191+ for (Object [] arr : optionGenderRaw ) {
192+ String option = arr [0 ] != null ? arr [0 ].toString () : null ;
193+ PollOptions opt = optionMap .get (option );
194+ if (opt == null ) continue ;
195+ Long pollItemsId = opt .getPollItemsId ();
196+ PollGenderStaticsDto .GenderCountDto dto = PollGenderStaticsDto .GenderCountDto .builder ()
197+ .option (option )
198+ .gender (arr [1 ] != null ? arr [1 ].toString () : null )
199+ .voteCount (arr [2 ] != null ? ((Number )arr [2 ]).longValue () : 0L )
200+ .build ();
201+ genderGroupMap .computeIfAbsent (pollItemsId , k -> new java .util .ArrayList <>()).add (dto );
202+ }
203+ java .util .List <PollGenderStaticsDto > optionGenderStatics = new java .util .ArrayList <>();
204+ for (int i = 0 ; i < options .size (); i ++) {
205+ PollOptions opt = options .get (i );
206+ optionGenderStatics .add (PollGenderStaticsDto .builder ()
207+ .pollItemsId (opt .getPollItemsId ())
208+ .pollOptionIndex (i + 1 )
209+ .genderCounts (genderGroupMap .getOrDefault (opt .getPollItemsId (), java .util .Collections .emptyList ()))
210+ .build ());
211+ }
212+ return PollStaticsResponseDto .builder ()
213+ .postId (postId )
214+ .pollId (pollId )
215+ .optionAgeStatics (optionAgeStatics )
216+ .optionGenderStatics (optionGenderStatics )
217+ .build ();
172218 }
173219
174220 // 최대 7일 동안 투표 가능 (초기 요구사항)
@@ -228,7 +274,9 @@ public List<PollDto> getTopNPollsByStatus(PollDto.PollStatus status, int n) {
228274 List <PollDto > pollDtos = new java .util .ArrayList <>();
229275 for (Object [] row : result ) {
230276 Long pollId = (Long ) row [0 ];
231- pollDtos .add (getPoll (pollId ));
277+ pollDtos .add (status == PollDto .PollStatus .CLOSED
278+ ? getPollWithStatistics (pollId )
279+ : getPoll (pollId ));
232280 }
233281 return pollDtos ;
234282 }
@@ -487,4 +535,37 @@ private void autoCloseIfNeeded(Poll poll) {
487535 public List <PollOptions > getPollOptions (Long pollId ) {
488536 return pollOptionsRepository .findByPoll_PollId (pollId );
489537 }
538+
539+ private static void validatePollCommon (String voteTitle , java .util .List <com .ai .lawyer .domain .poll .dto .PollOptionCreateDto > options , java .time .LocalDateTime reservedCloseAt ) {
540+ if (voteTitle == null || voteTitle .trim ().isEmpty ()) {
541+ throw new org .springframework .web .server .ResponseStatusException (org .springframework .http .HttpStatus .BAD_REQUEST , "투표 제목은 필수입니다." );
542+ }
543+ if (options == null || options .size () != 2 ) {
544+ throw new org .springframework .web .server .ResponseStatusException (org .springframework .http .HttpStatus .BAD_REQUEST , "투표 항목은 2개여야 합니다." );
545+ }
546+ for (com .ai .lawyer .domain .poll .dto .PollOptionCreateDto option : options ) {
547+ if (option .getContent () == null || option .getContent ().trim ().isEmpty ()) {
548+ throw new org .springframework .web .server .ResponseStatusException (org .springframework .http .HttpStatus .BAD_REQUEST , "각 투표 항목의 내용은 필수입니다." );
549+ }
550+ }
551+ java .time .LocalDateTime now = java .time .LocalDateTime .now ();
552+ if (reservedCloseAt != null ) {
553+ if (reservedCloseAt .isBefore (now .plusHours (1 ))) {
554+ throw new org .springframework .web .server .ResponseStatusException (org .springframework .http .HttpStatus .BAD_REQUEST , "예약 종료 시간은 현재로부터 최소 1시간 이후여야 합니다." );
555+ }
556+ if (reservedCloseAt .isAfter (now .plusDays (7 ))) {
557+ throw new org .springframework .web .server .ResponseStatusException (org .springframework .http .HttpStatus .BAD_REQUEST , "예약 종료 시간은 최대 7일 이내여야 합니다." );
558+ }
559+ }
560+ }
561+
562+ @ Override
563+ public void validatePollCreate (PollForPostDto dto ) {
564+ validatePollCommon (dto .getVoteTitle (), dto .getPollOptions (), dto .getReservedCloseAt ());
565+ }
566+
567+ @ Override
568+ public void validatePollCreate (PollCreateDto dto ) {
569+ validatePollCommon (dto .getVoteTitle (), dto .getPollOptions (), dto .getReservedCloseAt ());
570+ }
490571}
0 commit comments