From 23582934fac3a566bb81283422481a6d91dd286e Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:03:17 +0800 Subject: [PATCH] feat: track poll votes --- .../main/java/com/openisle/dto/PollDto.java | 2 +- .../java/com/openisle/mapper/PostMapper.java | 8 ++++- .../com/openisle/model/PollParticipant.java | 33 +++++++++++++++++++ .../java/com/openisle/model/PollPost.java | 7 ++-- .../com/openisle/service/PostService.java | 11 +++++-- 5 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/com/openisle/model/PollParticipant.java diff --git a/backend/src/main/java/com/openisle/dto/PollDto.java b/backend/src/main/java/com/openisle/dto/PollDto.java index 3612d60ed..107c027e8 100644 --- a/backend/src/main/java/com/openisle/dto/PollDto.java +++ b/backend/src/main/java/com/openisle/dto/PollDto.java @@ -12,5 +12,5 @@ public class PollDto { private List options; private Map votes; private LocalDateTime endTime; - private List participants; + private Map> participants; } diff --git a/backend/src/main/java/com/openisle/mapper/PostMapper.java b/backend/src/main/java/com/openisle/mapper/PostMapper.java index 5577dcdb8..825fa9584 100644 --- a/backend/src/main/java/com/openisle/mapper/PostMapper.java +++ b/backend/src/main/java/com/openisle/mapper/PostMapper.java @@ -6,10 +6,12 @@ import com.openisle.dto.PostSummaryDto; import com.openisle.dto.ReactionDto; import com.openisle.dto.LotteryDto; import com.openisle.dto.PollDto; +import com.openisle.dto.AuthorDto; import com.openisle.model.CommentSort; import com.openisle.model.Post; import com.openisle.model.LotteryPost; import com.openisle.model.PollPost; +import com.openisle.model.PollParticipant; import com.openisle.model.User; import com.openisle.service.CommentService; import com.openisle.service.ReactionService; @@ -19,6 +21,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** Mapper responsible for converting posts into DTOs. */ @@ -102,7 +105,10 @@ public class PostMapper { p.setOptions(pp.getOptions()); p.setVotes(pp.getVotes()); p.setEndTime(pp.getEndTime()); - p.setParticipants(pp.getParticipants().stream().map(userMapper::toAuthorDto).collect(Collectors.toList())); + Map> participants = pp.getParticipants().stream() + .collect(Collectors.groupingBy(PollParticipant::getOptionIndex, + Collectors.mapping(ppart -> userMapper.toAuthorDto(ppart.getUser()), Collectors.toList()))); + p.setParticipants(participants); dto.setPoll(p); } } diff --git a/backend/src/main/java/com/openisle/model/PollParticipant.java b/backend/src/main/java/com/openisle/model/PollParticipant.java new file mode 100644 index 000000000..0fd819e24 --- /dev/null +++ b/backend/src/main/java/com/openisle/model/PollParticipant.java @@ -0,0 +1,33 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Represents a single vote in a poll, capturing which user selected which option. + */ +@Entity +@Table(name = "poll_participants") +@Getter +@Setter +@NoArgsConstructor +public class PollParticipant { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private PollPost post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "option_index", nullable = false) + private int optionIndex; +} + diff --git a/backend/src/main/java/com/openisle/model/PollPost.java b/backend/src/main/java/com/openisle/model/PollPost.java index c19978c21..9746f4247 100644 --- a/backend/src/main/java/com/openisle/model/PollPost.java +++ b/backend/src/main/java/com/openisle/model/PollPost.java @@ -30,11 +30,8 @@ public class PollPost extends Post { @Column(name = "vote_count") private Map votes = new HashMap<>(); - @ManyToMany - @JoinTable(name = "poll_participants", - joinColumns = @JoinColumn(name = "post_id"), - inverseJoinColumns = @JoinColumn(name = "user_id")) - private Set participants = new HashSet<>(); + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private Set participants = new HashSet<>(); @Column private LocalDateTime endTime; diff --git a/backend/src/main/java/com/openisle/service/PostService.java b/backend/src/main/java/com/openisle/service/PostService.java index 296133b4c..f4d0d189b 100644 --- a/backend/src/main/java/com/openisle/service/PostService.java +++ b/backend/src/main/java/com/openisle/service/PostService.java @@ -10,6 +10,7 @@ import com.openisle.model.Comment; import com.openisle.model.NotificationType; import com.openisle.model.LotteryPost; import com.openisle.model.PollPost; +import com.openisle.model.PollParticipant; import com.openisle.repository.PostRepository; import com.openisle.repository.LotteryPostRepository; import com.openisle.repository.PollPostRepository; @@ -293,13 +294,19 @@ public class PostService { } User user = userRepository.findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); - if (post.getParticipants().contains(user)) { + boolean alreadyVoted = post.getParticipants().stream() + .anyMatch(p -> p.getUser().equals(user)); + if (alreadyVoted) { throw new IllegalArgumentException("User already voted"); } if (optionIndex < 0 || optionIndex >= post.getOptions().size()) { throw new IllegalArgumentException("Invalid option"); } - post.getParticipants().add(user); + PollParticipant participant = new PollParticipant(); + participant.setPost(post); + participant.setUser(user); + participant.setOptionIndex(optionIndex); + post.getParticipants().add(participant); post.getVotes().merge(optionIndex, 1, Integer::sum); return pollPostRepository.save(post); }