mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-06-13 01:06:20 +00:00
feat: add poll post type
This commit is contained in:
@@ -3,6 +3,7 @@ package com.openisle.controller;
|
|||||||
import com.openisle.dto.PostDetailDto;
|
import com.openisle.dto.PostDetailDto;
|
||||||
import com.openisle.dto.PostRequest;
|
import com.openisle.dto.PostRequest;
|
||||||
import com.openisle.dto.PostSummaryDto;
|
import com.openisle.dto.PostSummaryDto;
|
||||||
|
import com.openisle.dto.PollDto;
|
||||||
import com.openisle.mapper.PostMapper;
|
import com.openisle.mapper.PostMapper;
|
||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
@@ -42,7 +43,8 @@ public class PostController {
|
|||||||
req.getTitle(), req.getContent(), req.getTagIds(),
|
req.getTitle(), req.getContent(), req.getTagIds(),
|
||||||
req.getType(), req.getPrizeDescription(), req.getPrizeIcon(),
|
req.getType(), req.getPrizeDescription(), req.getPrizeIcon(),
|
||||||
req.getPrizeCount(), req.getPointCost(),
|
req.getPrizeCount(), req.getPointCost(),
|
||||||
req.getStartTime(), req.getEndTime());
|
req.getStartTime(), req.getEndTime(),
|
||||||
|
req.getQuestion(), req.getOptions());
|
||||||
draftService.deleteDraft(auth.getName());
|
draftService.deleteDraft(auth.getName());
|
||||||
PostDetailDto dto = postMapper.toDetailDto(post, auth.getName());
|
PostDetailDto dto = postMapper.toDetailDto(post, auth.getName());
|
||||||
dto.setReward(levelService.awardForPost(auth.getName()));
|
dto.setReward(levelService.awardForPost(auth.getName()));
|
||||||
@@ -86,6 +88,17 @@ public class PostController {
|
|||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/poll/progress")
|
||||||
|
public ResponseEntity<PollDto> pollProgress(@PathVariable Long id) {
|
||||||
|
return ResponseEntity.ok(postMapper.toSummaryDto(postService.getPoll(id)).getPoll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/poll/vote")
|
||||||
|
public ResponseEntity<Void> vote(@PathVariable Long id, @RequestParam("option") int option, Authentication auth) {
|
||||||
|
postService.votePoll(id, auth.getName(), option);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<PostSummaryDto> listPosts(@RequestParam(value = "categoryId", required = false) Long categoryId,
|
public List<PostSummaryDto> listPosts(@RequestParam(value = "categoryId", required = false) Long categoryId,
|
||||||
@RequestParam(value = "categoryIds", required = false) List<Long> categoryIds,
|
@RequestParam(value = "categoryIds", required = false) List<Long> categoryIds,
|
||||||
|
|||||||
16
backend/src/main/java/com/openisle/dto/PollDto.java
Normal file
16
backend/src/main/java/com/openisle/dto/PollDto.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.openisle.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PollDto {
|
||||||
|
private String question;
|
||||||
|
private List<String> options;
|
||||||
|
private Map<Integer, Integer> votes;
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
private List<AuthorDto> participants;
|
||||||
|
}
|
||||||
@@ -26,5 +26,8 @@ public class PostRequest {
|
|||||||
private Integer pointCost;
|
private Integer pointCost;
|
||||||
private LocalDateTime startTime;
|
private LocalDateTime startTime;
|
||||||
private LocalDateTime endTime;
|
private LocalDateTime endTime;
|
||||||
|
// fields for poll posts
|
||||||
|
private String question;
|
||||||
|
private List<String> options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class PostSummaryDto {
|
|||||||
private int pointReward;
|
private int pointReward;
|
||||||
private PostType type;
|
private PostType type;
|
||||||
private LotteryDto lottery;
|
private LotteryDto lottery;
|
||||||
|
private PollDto poll;
|
||||||
private boolean rssExcluded;
|
private boolean rssExcluded;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import com.openisle.dto.PostDetailDto;
|
|||||||
import com.openisle.dto.PostSummaryDto;
|
import com.openisle.dto.PostSummaryDto;
|
||||||
import com.openisle.dto.ReactionDto;
|
import com.openisle.dto.ReactionDto;
|
||||||
import com.openisle.dto.LotteryDto;
|
import com.openisle.dto.LotteryDto;
|
||||||
|
import com.openisle.dto.PollDto;
|
||||||
import com.openisle.model.CommentSort;
|
import com.openisle.model.CommentSort;
|
||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
import com.openisle.model.LotteryPost;
|
import com.openisle.model.LotteryPost;
|
||||||
|
import com.openisle.model.PollPost;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.service.CommentService;
|
import com.openisle.service.CommentService;
|
||||||
import com.openisle.service.ReactionService;
|
import com.openisle.service.ReactionService;
|
||||||
@@ -93,5 +95,15 @@ public class PostMapper {
|
|||||||
l.setWinners(lp.getWinners().stream().map(userMapper::toAuthorDto).collect(Collectors.toList()));
|
l.setWinners(lp.getWinners().stream().map(userMapper::toAuthorDto).collect(Collectors.toList()));
|
||||||
dto.setLottery(l);
|
dto.setLottery(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post instanceof PollPost pp) {
|
||||||
|
PollDto p = new PollDto();
|
||||||
|
p.setQuestion(pp.getQuestion());
|
||||||
|
p.setOptions(pp.getOptions());
|
||||||
|
p.setVotes(pp.getVotes());
|
||||||
|
p.setEndTime(pp.getEndTime());
|
||||||
|
p.setParticipants(pp.getParticipants().stream().map(userMapper::toAuthorDto).collect(Collectors.toList()));
|
||||||
|
dto.setPoll(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
backend/src/main/java/com/openisle/model/PollPost.java
Normal file
41
backend/src/main/java/com/openisle/model/PollPost.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package com.openisle.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "poll_posts")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@PrimaryKeyJoinColumn(name = "post_id")
|
||||||
|
public class PollPost extends Post {
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name = "poll_post_options", joinColumns = @JoinColumn(name = "post_id"))
|
||||||
|
@Column(name = "option_text")
|
||||||
|
private List<String> options = new ArrayList<>();
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name = "poll_post_votes", joinColumns = @JoinColumn(name = "post_id"))
|
||||||
|
@MapKeyColumn(name = "option_index")
|
||||||
|
@Column(name = "vote_count")
|
||||||
|
private Map<Integer, Integer> votes = new HashMap<>();
|
||||||
|
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(name = "poll_participants",
|
||||||
|
joinColumns = @JoinColumn(name = "post_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "user_id"))
|
||||||
|
private Set<User> participants = new HashSet<>();
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
}
|
||||||
@@ -2,5 +2,6 @@ package com.openisle.model;
|
|||||||
|
|
||||||
public enum PostType {
|
public enum PostType {
|
||||||
NORMAL,
|
NORMAL,
|
||||||
LOTTERY
|
LOTTERY,
|
||||||
|
POLL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.openisle.repository;
|
||||||
|
|
||||||
|
import com.openisle.model.PollPost;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface PollPostRepository extends JpaRepository<PollPost, Long> {
|
||||||
|
}
|
||||||
@@ -9,8 +9,10 @@ import com.openisle.model.Category;
|
|||||||
import com.openisle.model.Comment;
|
import com.openisle.model.Comment;
|
||||||
import com.openisle.model.NotificationType;
|
import com.openisle.model.NotificationType;
|
||||||
import com.openisle.model.LotteryPost;
|
import com.openisle.model.LotteryPost;
|
||||||
|
import com.openisle.model.PollPost;
|
||||||
import com.openisle.repository.PostRepository;
|
import com.openisle.repository.PostRepository;
|
||||||
import com.openisle.repository.LotteryPostRepository;
|
import com.openisle.repository.LotteryPostRepository;
|
||||||
|
import com.openisle.repository.PollPostRepository;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
import com.openisle.repository.CategoryRepository;
|
import com.openisle.repository.CategoryRepository;
|
||||||
import com.openisle.repository.TagRepository;
|
import com.openisle.repository.TagRepository;
|
||||||
@@ -54,6 +56,7 @@ public class PostService {
|
|||||||
private final CategoryRepository categoryRepository;
|
private final CategoryRepository categoryRepository;
|
||||||
private final TagRepository tagRepository;
|
private final TagRepository tagRepository;
|
||||||
private final LotteryPostRepository lotteryPostRepository;
|
private final LotteryPostRepository lotteryPostRepository;
|
||||||
|
private final PollPostRepository pollPostRepository;
|
||||||
private PublishMode publishMode;
|
private PublishMode publishMode;
|
||||||
private final NotificationService notificationService;
|
private final NotificationService notificationService;
|
||||||
private final SubscriptionService subscriptionService;
|
private final SubscriptionService subscriptionService;
|
||||||
@@ -78,6 +81,7 @@ public class PostService {
|
|||||||
CategoryRepository categoryRepository,
|
CategoryRepository categoryRepository,
|
||||||
TagRepository tagRepository,
|
TagRepository tagRepository,
|
||||||
LotteryPostRepository lotteryPostRepository,
|
LotteryPostRepository lotteryPostRepository,
|
||||||
|
PollPostRepository pollPostRepository,
|
||||||
NotificationService notificationService,
|
NotificationService notificationService,
|
||||||
SubscriptionService subscriptionService,
|
SubscriptionService subscriptionService,
|
||||||
CommentService commentService,
|
CommentService commentService,
|
||||||
@@ -97,6 +101,7 @@ public class PostService {
|
|||||||
this.categoryRepository = categoryRepository;
|
this.categoryRepository = categoryRepository;
|
||||||
this.tagRepository = tagRepository;
|
this.tagRepository = tagRepository;
|
||||||
this.lotteryPostRepository = lotteryPostRepository;
|
this.lotteryPostRepository = lotteryPostRepository;
|
||||||
|
this.pollPostRepository = pollPostRepository;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
this.subscriptionService = subscriptionService;
|
this.subscriptionService = subscriptionService;
|
||||||
this.commentService = commentService;
|
this.commentService = commentService;
|
||||||
@@ -166,7 +171,9 @@ public class PostService {
|
|||||||
Integer prizeCount,
|
Integer prizeCount,
|
||||||
Integer pointCost,
|
Integer pointCost,
|
||||||
LocalDateTime startTime,
|
LocalDateTime startTime,
|
||||||
LocalDateTime endTime) {
|
LocalDateTime endTime,
|
||||||
|
String question,
|
||||||
|
java.util.List<String> options) {
|
||||||
long recent = postRepository.countByAuthorAfter(username,
|
long recent = postRepository.countByAuthorAfter(username,
|
||||||
java.time.LocalDateTime.now().minusMinutes(5));
|
java.time.LocalDateTime.now().minusMinutes(5));
|
||||||
if (recent >= 1) {
|
if (recent >= 1) {
|
||||||
@@ -200,6 +207,15 @@ public class PostService {
|
|||||||
lp.setStartTime(startTime);
|
lp.setStartTime(startTime);
|
||||||
lp.setEndTime(endTime);
|
lp.setEndTime(endTime);
|
||||||
post = lp;
|
post = lp;
|
||||||
|
} else if (actualType == PostType.POLL) {
|
||||||
|
if (options == null || options.size() < 2) {
|
||||||
|
throw new IllegalArgumentException("At least two options required");
|
||||||
|
}
|
||||||
|
PollPost pp = new PollPost();
|
||||||
|
pp.setQuestion(question);
|
||||||
|
pp.setOptions(options);
|
||||||
|
pp.setEndTime(endTime);
|
||||||
|
post = pp;
|
||||||
} else {
|
} else {
|
||||||
post = new Post();
|
post = new Post();
|
||||||
}
|
}
|
||||||
@@ -212,6 +228,8 @@ public class PostService {
|
|||||||
post.setStatus(publishMode == PublishMode.REVIEW ? PostStatus.PENDING : PostStatus.PUBLISHED);
|
post.setStatus(publishMode == PublishMode.REVIEW ? PostStatus.PENDING : PostStatus.PUBLISHED);
|
||||||
if (post instanceof LotteryPost) {
|
if (post instanceof LotteryPost) {
|
||||||
post = lotteryPostRepository.save((LotteryPost) post);
|
post = lotteryPostRepository.save((LotteryPost) post);
|
||||||
|
} else if (post instanceof PollPost) {
|
||||||
|
post = pollPostRepository.save((PollPost) post);
|
||||||
} else {
|
} else {
|
||||||
post = postRepository.save(post);
|
post = postRepository.save(post);
|
||||||
}
|
}
|
||||||
@@ -261,6 +279,31 @@ public class PostService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PollPost getPoll(Long postId) {
|
||||||
|
return pollPostRepository.findById(postId)
|
||||||
|
.orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public PollPost votePoll(Long postId, String username, int optionIndex) {
|
||||||
|
PollPost post = pollPostRepository.findById(postId)
|
||||||
|
.orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found"));
|
||||||
|
if (post.getEndTime() != null && post.getEndTime().isBefore(LocalDateTime.now())) {
|
||||||
|
throw new IllegalStateException("Poll has ended");
|
||||||
|
}
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
|
||||||
|
if (post.getParticipants().contains(user)) {
|
||||||
|
throw new IllegalArgumentException("User already voted");
|
||||||
|
}
|
||||||
|
if (optionIndex < 0 || optionIndex >= post.getOptions().size()) {
|
||||||
|
throw new IllegalArgumentException("Invalid option");
|
||||||
|
}
|
||||||
|
post.getParticipants().add(user);
|
||||||
|
post.getVotes().merge(optionIndex, 1, Integer::sum);
|
||||||
|
return pollPostRepository.save(post);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void finalizeLottery(Long postId) {
|
public void finalizeLottery(Long postId) {
|
||||||
log.info("start to finalizeLottery for {}", postId);
|
log.info("start to finalizeLottery for {}", postId);
|
||||||
|
|||||||
Reference in New Issue
Block a user