From 4969a759aa3d3043d05025eb4a1bbb8e2d533b52 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 20 Aug 2025 19:33:31 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E5=B7=B2=E5=85=B3=E9=97=AD=E7=9A=84?= =?UTF-8?q?=E5=B8=96=E5=AD=90=E4=B8=8D=E9=9C=80=E8=A6=81=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E6=8C=89=E9=92=AE=20#651?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend_nuxt/pages/posts/[id]/index.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend_nuxt/pages/posts/[id]/index.vue b/frontend_nuxt/pages/posts/[id]/index.vue index 128b8a208..b117e6e20 100644 --- a/frontend_nuxt/pages/posts/[id]/index.vue +++ b/frontend_nuxt/pages/posts/[id]/index.vue @@ -17,7 +17,7 @@
已拒绝
已关闭
@@ -27,7 +27,7 @@
@@ -1067,6 +1067,7 @@ onMounted(async () => { white-space: nowrap; } +.article-closed-button, .article-subscribe-button-text, .article-unsubscribe-button-text { white-space: nowrap; From 91ffacc3358886c785cd23bb5f940b0ed9b48437 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 20 Aug 2025 19:43:31 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=E5=B7=B2=E7=BB=8F=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=9A=84=E5=B8=96=E5=AD=90=20=E9=87=8D=E6=96=B0=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=20=E6=B2=A1=E6=9C=89=E6=89=A7=E8=A1=8C=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=AE=9A=E4=BD=8D=E9=80=BB=E8=BE=91=20#652?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend_nuxt/pages/posts/[id]/index.vue | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend_nuxt/pages/posts/[id]/index.vue b/frontend_nuxt/pages/posts/[id]/index.vue index b117e6e20..45f4e9bbe 100644 --- a/frontend_nuxt/pages/posts/[id]/index.vue +++ b/frontend_nuxt/pages/posts/[id]/index.vue @@ -876,12 +876,7 @@ const gotoProfile = () => { navigateTo(`/users/${author.value.id}`, { replace: true }) } -onActivated(async () => { - await refreshPost() - await fetchComments() -}) - -onMounted(async () => { +const initPage = async () => { await fetchComments() const hash = location.hash const id = hash.startsWith('#comment-') ? hash.substring('#comment-'.length) : null @@ -889,6 +884,14 @@ onMounted(async () => { updateCurrentIndex() window.addEventListener('scroll', updateCurrentIndex) jumpToHashComment() +} + +onActivated(async () => { + await initPage() +}) + +onMounted(async () => { + await initPage() }) From 959b0f6a489e5e63a21d794b77a13547e2022e97 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:21:31 +0800 Subject: [PATCH 3/5] feat: notify authors when admin deletes post --- .../com/openisle/model/NotificationType.java | 2 + .../com/openisle/service/PostService.java | 9 +++- .../com/openisle/service/PostServiceTest.java | 52 +++++++++++++++++++ frontend_nuxt/pages/message.vue | 20 +++++++ frontend_nuxt/utils/notification.js | 13 +++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/openisle/model/NotificationType.java b/backend/src/main/java/com/openisle/model/NotificationType.java index 132af5176..d8e3a99a1 100644 --- a/backend/src/main/java/com/openisle/model/NotificationType.java +++ b/backend/src/main/java/com/openisle/model/NotificationType.java @@ -14,6 +14,8 @@ public enum NotificationType { POST_REVIEW_REQUEST, /** Your post under review was approved or rejected */ POST_REVIEWED, + /** An administrator deleted your post */ + POST_DELETED, /** A subscribed post received a new comment */ POST_UPDATED, /** Someone subscribed to your post */ diff --git a/backend/src/main/java/com/openisle/service/PostService.java b/backend/src/main/java/com/openisle/service/PostService.java index e9a205fb9..f0238d29e 100644 --- a/backend/src/main/java/com/openisle/service/PostService.java +++ b/backend/src/main/java/com/openisle/service/PostService.java @@ -579,7 +579,9 @@ public class PostService { .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); User user = userRepository.findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); - if (!user.getId().equals(post.getAuthor().getId()) && user.getRole() != Role.ADMIN) { + User author = post.getAuthor(); + boolean adminDeleting = !user.getId().equals(author.getId()) && user.getRole() == Role.ADMIN; + if (!user.getId().equals(author.getId()) && user.getRole() != Role.ADMIN) { throw new IllegalArgumentException("Unauthorized"); } for (Comment c : commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post)) { @@ -596,7 +598,12 @@ public class PostService { future.cancel(false); } } + String title = post.getTitle(); postRepository.delete(post); + if (adminDeleting) { + notificationService.createNotification(author, NotificationType.POST_DELETED, + null, null, null, user, null, title); + } } public java.util.List getPostsByIds(java.util.List ids) { diff --git a/backend/src/test/java/com/openisle/service/PostServiceTest.java b/backend/src/test/java/com/openisle/service/PostServiceTest.java index e1dbfd297..4ad621b18 100644 --- a/backend/src/test/java/com/openisle/service/PostServiceTest.java +++ b/backend/src/test/java/com/openisle/service/PostServiceTest.java @@ -61,6 +61,58 @@ class PostServiceTest { verify(postRepo).delete(post); } + @Test + void deletePostByAdminNotifiesAuthor() { + PostRepository postRepo = mock(PostRepository.class); + UserRepository userRepo = mock(UserRepository.class); + CategoryRepository catRepo = mock(CategoryRepository.class); + TagRepository tagRepo = mock(TagRepository.class); + LotteryPostRepository lotteryRepo = mock(LotteryPostRepository.class); + NotificationService notifService = mock(NotificationService.class); + SubscriptionService subService = mock(SubscriptionService.class); + CommentService commentService = mock(CommentService.class); + CommentRepository commentRepo = mock(CommentRepository.class); + ReactionRepository reactionRepo = mock(ReactionRepository.class); + PostSubscriptionRepository subRepo = mock(PostSubscriptionRepository.class); + NotificationRepository notificationRepo = mock(NotificationRepository.class); + PostReadService postReadService = mock(PostReadService.class); + ImageUploader imageUploader = mock(ImageUploader.class); + TaskScheduler taskScheduler = mock(TaskScheduler.class); + EmailSender emailSender = mock(EmailSender.class); + ApplicationContext context = mock(ApplicationContext.class); + + PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo, + notifService, subService, commentService, commentRepo, + reactionRepo, subRepo, notificationRepo, postReadService, + imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT); + when(context.getBean(PostService.class)).thenReturn(service); + + Post post = new Post(); + post.setId(1L); + post.setTitle("T"); + post.setContent(""); + User author = new User(); + author.setId(2L); + author.setRole(Role.USER); + post.setAuthor(author); + + User admin = new User(); + admin.setId(1L); + admin.setRole(Role.ADMIN); + + when(postRepo.findById(1L)).thenReturn(Optional.of(post)); + when(userRepo.findByUsername("admin")).thenReturn(Optional.of(admin)); + when(commentRepo.findByPostAndParentIsNullOrderByCreatedAtAsc(post)).thenReturn(List.of()); + when(reactionRepo.findByPost(post)).thenReturn(List.of()); + when(subRepo.findByPost(post)).thenReturn(List.of()); + when(notificationRepo.findByPost(post)).thenReturn(List.of()); + + service.deletePost(1L, "admin"); + + verify(notifService).createNotification(eq(author), eq(NotificationType.POST_DELETED), isNull(), + isNull(), isNull(), eq(admin), isNull(), eq("T")); + } + @Test void createPostRespectsRateLimit() { PostRepository postRepo = mock(PostRepository.class); diff --git a/frontend_nuxt/pages/message.vue b/frontend_nuxt/pages/message.vue index 4b89b6ec5..096d3b935 100644 --- a/frontend_nuxt/pages/message.vue +++ b/frontend_nuxt/pages/message.vue @@ -495,6 +495,24 @@ 已被管理员拒绝 +