mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-07 07:30:54 +08:00
Compare commits
8 Commits
codex/modi
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f152906f2 | ||
|
|
ef71d0b3d4 | ||
|
|
6f80d139ba | ||
|
|
7454931fa5 | ||
|
|
0852664a82 | ||
|
|
b1998be425 | ||
|
|
72adc5b232 | ||
|
|
d24e67de5d |
@@ -5,6 +5,8 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.Where;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -13,6 +15,8 @@ import java.time.LocalDateTime;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "comments")
|
||||
@SQLDelete(sql = "UPDATE comments SET deleted_at = CURRENT_TIMESTAMP(6) WHERE id = ?")
|
||||
@Where(clause = "deleted_at IS NULL")
|
||||
public class Comment {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -41,4 +45,7 @@ public class Comment {
|
||||
@Column
|
||||
private LocalDateTime pinnedAt;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.Where;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -13,6 +15,8 @@ import java.time.LocalDateTime;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "point_histories")
|
||||
@SQLDelete(sql = "UPDATE point_histories SET deleted_at = CURRENT_TIMESTAMP(6) WHERE id = ?")
|
||||
@Where(clause = "deleted_at IS NULL")
|
||||
public class PointHistory {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -46,4 +50,7 @@ public class PointHistory {
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.openisle.repository;
|
||||
|
||||
import com.openisle.model.PointHistory;
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.model.Comment;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -12,4 +13,6 @@ public interface PointHistoryRepository extends JpaRepository<PointHistory, Long
|
||||
long countByUser(User user);
|
||||
|
||||
List<PointHistory> findByUserAndCreatedAtAfterOrderByCreatedAtDesc(User user, LocalDateTime createdAt);
|
||||
|
||||
List<PointHistory> findByComment(Comment comment);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.openisle.repository.UserRepository;
|
||||
import com.openisle.repository.ReactionRepository;
|
||||
import com.openisle.repository.CommentSubscriptionRepository;
|
||||
import com.openisle.repository.NotificationRepository;
|
||||
import com.openisle.repository.PointHistoryRepository;
|
||||
import com.openisle.service.NotificationService;
|
||||
import com.openisle.service.SubscriptionService;
|
||||
import com.openisle.model.Role;
|
||||
@@ -37,6 +38,7 @@ public class CommentService {
|
||||
private final ReactionRepository reactionRepository;
|
||||
private final CommentSubscriptionRepository commentSubscriptionRepository;
|
||||
private final NotificationRepository notificationRepository;
|
||||
private final PointHistoryRepository pointHistoryRepository;
|
||||
private final ImageUploader imageUploader;
|
||||
|
||||
@Transactional
|
||||
@@ -235,10 +237,14 @@ public class CommentService {
|
||||
for (Comment c : replies) {
|
||||
deleteCommentCascade(c);
|
||||
}
|
||||
// 逻辑删除相关的积分历史记录
|
||||
pointHistoryRepository.findByComment(comment).forEach(pointHistoryRepository::delete);
|
||||
// 删除其他相关数据
|
||||
reactionRepository.findByComment(comment).forEach(reactionRepository::delete);
|
||||
commentSubscriptionRepository.findByComment(comment).forEach(commentSubscriptionRepository::delete);
|
||||
notificationRepository.deleteAll(notificationRepository.findByComment(comment));
|
||||
imageUploader.removeReferences(imageUploader.extractUrls(comment.getContent()));
|
||||
// 逻辑删除评论
|
||||
commentRepository.delete(comment);
|
||||
log.debug("deleteCommentCascade removed comment {}", comment.getId());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Add logical delete support for comments and point_histories tables
|
||||
|
||||
-- Add deleted_at column to comments table
|
||||
ALTER TABLE comments ADD COLUMN deleted_at DATETIME(6) NULL;
|
||||
|
||||
-- Add deleted_at column to point_histories table
|
||||
ALTER TABLE point_histories ADD COLUMN deleted_at DATETIME(6) NULL;
|
||||
|
||||
-- Add index for better performance on logical delete queries
|
||||
CREATE INDEX idx_comments_deleted_at ON comments(deleted_at);
|
||||
CREATE INDEX idx_point_histories_deleted_at ON point_histories(deleted_at);
|
||||
@@ -6,6 +6,7 @@ import com.openisle.repository.UserRepository;
|
||||
import com.openisle.repository.ReactionRepository;
|
||||
import com.openisle.repository.CommentSubscriptionRepository;
|
||||
import com.openisle.repository.NotificationRepository;
|
||||
import com.openisle.repository.PointHistoryRepository;
|
||||
import com.openisle.exception.RateLimitException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -24,10 +25,11 @@ class CommentServiceTest {
|
||||
ReactionRepository reactionRepo = mock(ReactionRepository.class);
|
||||
CommentSubscriptionRepository subRepo = mock(CommentSubscriptionRepository.class);
|
||||
NotificationRepository nRepo = mock(NotificationRepository.class);
|
||||
PointHistoryRepository pointHistoryRepo = mock(PointHistoryRepository.class);
|
||||
ImageUploader imageUploader = mock(ImageUploader.class);
|
||||
|
||||
CommentService service = new CommentService(commentRepo, postRepo, userRepo,
|
||||
notifService, subService, reactionRepo, subRepo, nRepo, imageUploader);
|
||||
notifService, subService, reactionRepo, subRepo, nRepo, pointHistoryRepo, imageUploader);
|
||||
|
||||
when(commentRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(3L);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ class PostServiceTest {
|
||||
CategoryRepository catRepo = mock(CategoryRepository.class);
|
||||
TagRepository tagRepo = mock(TagRepository.class);
|
||||
LotteryPostRepository lotteryRepo = mock(LotteryPostRepository.class);
|
||||
PollPostRepository pollPostRepo = mock(PollPostRepository.class);
|
||||
PollVoteRepository pollVoteRepo = mock(PollVoteRepository.class);
|
||||
NotificationService notifService = mock(NotificationService.class);
|
||||
SubscriptionService subService = mock(SubscriptionService.class);
|
||||
CommentService commentService = mock(CommentService.class);
|
||||
@@ -37,7 +39,7 @@ class PostServiceTest {
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
@@ -69,6 +71,8 @@ class PostServiceTest {
|
||||
CategoryRepository catRepo = mock(CategoryRepository.class);
|
||||
TagRepository tagRepo = mock(TagRepository.class);
|
||||
LotteryPostRepository lotteryRepo = mock(LotteryPostRepository.class);
|
||||
PollPostRepository pollPostRepo = mock(PollPostRepository.class);
|
||||
PollVoteRepository pollVoteRepo = mock(PollVoteRepository.class);
|
||||
NotificationService notifService = mock(NotificationService.class);
|
||||
SubscriptionService subService = mock(SubscriptionService.class);
|
||||
CommentService commentService = mock(CommentService.class);
|
||||
@@ -84,7 +88,7 @@ class PostServiceTest {
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
@@ -122,6 +126,8 @@ class PostServiceTest {
|
||||
CategoryRepository catRepo = mock(CategoryRepository.class);
|
||||
TagRepository tagRepo = mock(TagRepository.class);
|
||||
LotteryPostRepository lotteryRepo = mock(LotteryPostRepository.class);
|
||||
PollPostRepository pollPostRepo = mock(PollPostRepository.class);
|
||||
PollVoteRepository pollVoteRepo = mock(PollVoteRepository.class);
|
||||
NotificationService notifService = mock(NotificationService.class);
|
||||
SubscriptionService subService = mock(SubscriptionService.class);
|
||||
CommentService commentService = mock(CommentService.class);
|
||||
@@ -137,7 +143,7 @@ class PostServiceTest {
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
@@ -156,6 +162,8 @@ class PostServiceTest {
|
||||
CategoryRepository catRepo = mock(CategoryRepository.class);
|
||||
TagRepository tagRepo = mock(TagRepository.class);
|
||||
LotteryPostRepository lotteryRepo = mock(LotteryPostRepository.class);
|
||||
PollPostRepository pollPostRepo = mock(PollPostRepository.class);
|
||||
PollVoteRepository pollVoteRepo = mock(PollVoteRepository.class);
|
||||
NotificationService notifService = mock(NotificationService.class);
|
||||
SubscriptionService subService = mock(SubscriptionService.class);
|
||||
CommentService commentService = mock(CommentService.class);
|
||||
@@ -171,7 +179,7 @@ class PostServiceTest {
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
pollPostRepo, pollVoteRepo, notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="poll-title-section">
|
||||
<div class="poll-option-title" v-if="poll.multiple">多选</div>
|
||||
<div class="poll-option-title" v-else>单选</div>
|
||||
|
||||
<div class="poll-left-time">
|
||||
<div class="poll-left-time-title">离结束还有</div>
|
||||
<div class="poll-left-time-value">{{ countdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="poll.multiple">
|
||||
<div
|
||||
v-for="(opt, idx) in poll.options"
|
||||
@@ -45,12 +54,8 @@
|
||||
</div>
|
||||
|
||||
<div class="multi-selection-container">
|
||||
<div class="multi-selection-title">
|
||||
<i class="fas fa-info-circle info-icon"></i>
|
||||
该投票为多选
|
||||
</div>
|
||||
<div class="join-poll-button" @click="submitMultiPoll">
|
||||
<i class="fas fa-plus"></i> 加入投票
|
||||
<i class="fas fa-check"></i> 确认投票
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -71,12 +76,8 @@
|
||||
</div>
|
||||
|
||||
<div class="single-selection-container">
|
||||
<div class="single-selection-title">
|
||||
<i class="fas fa-info-circle info-icon"></i>
|
||||
该投票为单选
|
||||
</div>
|
||||
<div class="join-poll-button" @click="submitSinglePoll">
|
||||
<i class="fas fa-plus"></i> 加入投票
|
||||
<i class="fas fa-check"></i> 确认投票
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -102,10 +103,11 @@
|
||||
>
|
||||
<i class="fas fa-chart-bar"></i> 结果
|
||||
</div>
|
||||
|
||||
<div class="poll-left-time">
|
||||
<div class="poll-left-time-title">离结束还有</div>
|
||||
<div class="poll-left-time-value">{{ countdown }}</div>
|
||||
<div v-else-if="pollEnded" class="poll-option-hint">
|
||||
<i class="fas fa-stopwatch"></i> 投票已结束
|
||||
</div>
|
||||
<div v-else class="poll-option-hint">
|
||||
<i class="fas fa-stopwatch"></i> 您已投票,等待结束查看结果
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -397,7 +399,8 @@ const submitMultiPoll = async () => {
|
||||
|
||||
.multi-selection-container,
|
||||
.single-selection-container {
|
||||
padding: 20px 15px 20px 5px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
@@ -409,6 +412,22 @@ const submitMultiPoll = async () => {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.poll-title-section {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
flex-direction: row;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.poll-option-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.poll-left-time {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user