From cb4de36f2c0e4422b4bcbd0c83bb58ad119a3a25 Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Tue, 11 Feb 2025 11:04:08 +0100
Subject: [PATCH 1/5] Refactor so everyone goes through BasicForumService to
 get forum related data

---
 .../dsv/scipro/forum/BasicForumService.java   |  4 ++++
 .../scipro/forum/BasicForumServiceImpl.java   | 20 ++++++++++++++++++-
 .../scipro/forum/dataobjects/ForumThread.java |  9 ---------
 .../panels/threaded/ThreadsOverviewPanel.java | 16 +++++++--------
 .../threaded/ThreadsOverviewPanelTest.java    |  5 ++++-
 5 files changed, 35 insertions(+), 19 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
index 6a680ad26f..0d93d6c37a 100644
--- a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
+++ b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
@@ -23,4 +23,8 @@ public interface BasicForumService extends Serializable {
     ForumThread createThread(String subject);
 
     long countUnreadThreads(List<ForumThread> forumThreadList, User user);
+
+    ForumPost getLastPost(ForumThread forumThread);
+
+    boolean hasAttachments(ForumThread forumThread);
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
index c48e3cab91..0e7c7cbaaf 100644
--- a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
@@ -66,7 +66,7 @@ public class BasicForumServiceImpl implements BasicForumService {
 
     @Override
     public boolean isThreadRead(User user, ForumThread forumThread) {
-        for (ForumPost post : forumThread.getPosts()) {
+        for (ForumPost post : getPosts(forumThread)) {
             if (!getReadState(user, post).isRead()) {
                 return false;
             }
@@ -133,4 +133,22 @@ public class BasicForumServiceImpl implements BasicForumService {
 
         return post;
     }
+
+    @Override
+    public ForumPost getLastPost(ForumThread forumThread) {
+        return Collections.max(
+            getPosts(forumThread),
+            Comparator.comparing(ForumPost::getDateCreated).thenComparing(ForumPost::getId)
+        );
+    }
+
+    @Override
+    public boolean hasAttachments(ForumThread forumThread) {
+        for (ForumPost post : getPosts(forumThread)) {
+            if (!post.getAttachments().isEmpty()) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/forum/dataobjects/ForumThread.java b/core/src/main/java/se/su/dsv/scipro/forum/dataobjects/ForumThread.java
index 1c61c2c563..f1933e89c0 100644
--- a/core/src/main/java/se/su/dsv/scipro/forum/dataobjects/ForumThread.java
+++ b/core/src/main/java/se/su/dsv/scipro/forum/dataobjects/ForumThread.java
@@ -116,13 +116,4 @@ public class ForumThread extends LazyDeletableDomainObject {
     public User getCreatedBy() {
         return getPosts().get(0).getPostedBy();
     }
-
-    public boolean hasAttachments() {
-        for (ForumPost post : posts) {
-            if (!post.getAttachments().isEmpty()) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanel.java b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanel.java
index 02a9d00bd5..d0e7e1c55d 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanel.java
@@ -1,8 +1,7 @@
 package se.su.dsv.scipro.forum.panels.threaded;
 
+import jakarta.inject.Inject;
 import java.io.Serializable;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
@@ -15,6 +14,7 @@ import org.apache.wicket.model.LambdaModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import se.su.dsv.scipro.components.DateLabel;
 import se.su.dsv.scipro.data.enums.DateStyle;
+import se.su.dsv.scipro.forum.BasicForumService;
 import se.su.dsv.scipro.forum.Discussable;
 import se.su.dsv.scipro.forum.dataobjects.ForumPost;
 import se.su.dsv.scipro.forum.dataobjects.ForumThread;
@@ -23,6 +23,9 @@ import se.su.dsv.scipro.system.User;
 
 public class ThreadsOverviewPanel<A> extends Panel {
 
+    @Inject
+    private BasicForumService basicForumService;
+
     public ThreadsOverviewPanel(
         final String id,
         final IModel<List<A>> model,
@@ -41,7 +44,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
                             @Override
                             protected void onConfigure() {
                                 super.onConfigure();
-                                setVisibilityAllowed(discussion.getObject().hasAttachments());
+                                setVisibilityAllowed(basicForumService.hasAttachments(discussion.getObject()));
                             }
                         }
                     );
@@ -80,7 +83,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
         BookmarkablePageLink<Void> newThreadLink(String id, IModel<A> thread);
     }
 
-    private static class LastPostColumn extends WebMarkupContainer {
+    private class LastPostColumn extends WebMarkupContainer {
 
         public LastPostColumn(String id, final IModel<ForumThread> model) {
             super(id);
@@ -110,10 +113,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
             return new LoadableDetachableModel<>() {
                 @Override
                 protected ForumPost load() {
-                    return Collections.max(
-                        model.getObject().getPosts(),
-                        Comparator.comparing(ForumPost::getDateCreated).thenComparing(ForumPost::getId)
-                    );
+                    return basicForumService.getLastPost(model.getObject());
                 }
             };
         }
diff --git a/view/src/test/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanelTest.java b/view/src/test/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanelTest.java
index 8c99db6c4f..614f92e7cd 100644
--- a/view/src/test/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/forum/panels/threaded/ThreadsOverviewPanelTest.java
@@ -9,6 +9,7 @@ import org.apache.wicket.model.Model;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
 import se.su.dsv.scipro.SciProTest;
 import se.su.dsv.scipro.forum.Discussable;
@@ -20,11 +21,13 @@ import se.su.dsv.scipro.system.User;
 public class ThreadsOverviewPanelTest extends SciProTest {
 
     private List<ForumThread> threads;
+    private ForumPost post;
 
     @BeforeEach
     public void setUp() throws Exception {
         ForumThread forumThread = createThread();
         threads = Arrays.asList(forumThread);
+        Mockito.when(basicForumService.getLastPost(forumThread)).thenReturn(post);
     }
 
     @Test
@@ -54,7 +57,7 @@ public class ThreadsOverviewPanelTest extends SciProTest {
 
     private ForumThread createThread() {
         User bob = User.builder().firstName("Bob").lastName("the Builder").emailAddress("bob@building.com").build();
-        ForumPost post = new ForumPost();
+        post = new ForumPost();
         post.setPostedBy(bob);
         ForumThread groupForumThread = new ForumThread();
         groupForumThread.addPost(post);
-- 
2.39.5


From 3933cfa2be3e78eb802e542c9393dda9581f097e Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Tue, 11 Feb 2025 14:53:10 +0100
Subject: [PATCH 2/5] Allow deletion of forum posts

Only your own posts can be deleted and never the initial post in a thread.
---
 .../java/se/su/dsv/scipro/CoreConfig.java     |  6 ++-
 .../dsv/scipro/forum/BasicForumService.java   |  4 ++
 .../scipro/forum/BasicForumServiceImpl.java   | 40 ++++++++++++++++++-
 .../forum/panels/threaded/ForumPostPanel.html |  5 ++-
 .../forum/panels/threaded/ForumPostPanel.java | 30 ++++++++++++++
 .../panels/threaded/ViewForumThreadPanel.java | 11 ++++-
 .../dsv/scipro/reviewer/timeline/Event.java   |  8 +++-
 7 files changed, 97 insertions(+), 7 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index b7c51b41f2..6a84a1a502 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -348,14 +348,16 @@ public class CoreConfig {
         ForumPostReadStateRepository readStateRepository,
         AbstractThreadRepository threadRepository,
         FileService fileService,
-        EventBus eventBus
+        EventBus eventBus,
+        CurrentUser currentUser
     ) {
         return new BasicForumServiceImpl(
             forumPostRepository,
             readStateRepository,
             threadRepository,
             fileService,
-            eventBus
+            eventBus,
+            currentUser
         );
     }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
index 0d93d6c37a..44ff9f407a 100644
--- a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
+++ b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumService.java
@@ -27,4 +27,8 @@ public interface BasicForumService extends Serializable {
     ForumPost getLastPost(ForumThread forumThread);
 
     boolean hasAttachments(ForumThread forumThread);
+
+    boolean canDelete(ForumPost forumPost);
+
+    void deletePost(ForumPost post);
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
index 0e7c7cbaaf..c74a6fecab 100644
--- a/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/forum/BasicForumServiceImpl.java
@@ -10,6 +10,7 @@ import se.su.dsv.scipro.file.FileService;
 import se.su.dsv.scipro.forum.dataobjects.ForumPost;
 import se.su.dsv.scipro.forum.dataobjects.ForumPostReadState;
 import se.su.dsv.scipro.forum.dataobjects.ForumThread;
+import se.su.dsv.scipro.system.CurrentUser;
 import se.su.dsv.scipro.system.User;
 
 public class BasicForumServiceImpl implements BasicForumService {
@@ -19,6 +20,7 @@ public class BasicForumServiceImpl implements BasicForumService {
     private final ForumPostReadStateRepository readStateRepository;
     private final FileService fileService;
     private final EventBus eventBus;
+    private final CurrentUser currentUserProvider;
 
     @Inject
     public BasicForumServiceImpl(
@@ -26,13 +28,15 @@ public class BasicForumServiceImpl implements BasicForumService {
         final ForumPostReadStateRepository readStateRepository,
         AbstractThreadRepository threadRepository,
         final FileService fileService,
-        final EventBus eventBus
+        final EventBus eventBus,
+        final CurrentUser currentUserProvider
     ) {
         this.postRepository = postRepository;
         this.readStateRepository = readStateRepository;
         this.threadRepository = threadRepository;
         this.fileService = fileService;
         this.eventBus = eventBus;
+        this.currentUserProvider = currentUserProvider;
     }
 
     @Override
@@ -151,4 +155,38 @@ public class BasicForumServiceImpl implements BasicForumService {
         }
         return false;
     }
+
+    @Override
+    public boolean canDelete(ForumPost forumPost) {
+        ForumPost initialPost = forumPost.getForumThread().getPosts().get(0);
+        if (forumPost.equals(initialPost)) {
+            // The initial post in a thread can never be deleted
+            return false;
+        }
+
+        User user = currentUserProvider.get();
+        // Current user can be null meaning the call came from the system
+        if (user == null) {
+            // Allow the system to delete any post
+            return true;
+        }
+        return Objects.equals(forumPost.getPostedBy(), user);
+    }
+
+    @Override
+    @Transactional
+    public void deletePost(ForumPost post) {
+        if (!canDelete(post)) {
+            throw new PostCantBeDeletedException();
+        }
+        post.setDeleted(true);
+        postRepository.save(post);
+    }
+
+    private static final class PostCantBeDeletedException extends IllegalArgumentException {
+
+        public PostCantBeDeletedException() {
+            super("User is not allowed to delete post");
+        }
+    }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.html b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.html
index eba931f634..b09f0405ca 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.html
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.html
@@ -7,9 +7,10 @@
 <body>
 <wicket:panel>
     <div class="messageWrap">
-        <div class="forumBlueBackground">
+        <div class="forumBlueBackground d-flex justify-content-between">
             <!-- DATE ROW-->
-            <wicket:container wicket:id="dateCreated"/>
+            <span wicket:id="dateCreated"></span>
+            <button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
         </div>
         <div class="forumGrayBackground">
             <div class="vertAlign">
diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
index 004d99561a..c86fbe18f6 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
@@ -1,7 +1,9 @@
 package se.su.dsv.scipro.forum.panels.threaded;
 
+import jakarta.inject.Inject;
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LambdaModel;
@@ -11,9 +13,11 @@ import se.su.dsv.scipro.components.ListAdapterModel;
 import se.su.dsv.scipro.components.SmarterLinkMultiLineLabel;
 import se.su.dsv.scipro.data.enums.DateStyle;
 import se.su.dsv.scipro.file.FileReference;
+import se.su.dsv.scipro.forum.BasicForumService;
 import se.su.dsv.scipro.forum.dataobjects.ForumPost;
 import se.su.dsv.scipro.profile.UserLinkPanel;
 import se.su.dsv.scipro.repository.panels.ViewAttachmentPanel;
+import se.su.dsv.scipro.session.SciProSession;
 
 public class ForumPostPanel extends Panel {
 
@@ -22,6 +26,9 @@ public class ForumPostPanel extends Panel {
     public static final String CONTENT = "content";
     public static final String ATTACHMENT = "attachment";
 
+    @Inject
+    private BasicForumService basicForumService;
+
     public ForumPostPanel(String id, final IModel<ForumPost> model) {
         super(id);
         add(new UserLinkPanel(POSTED_BY, LambdaModel.of(model, ForumPost::getPostedBy, ForumPost::setPostedBy)));
@@ -62,5 +69,28 @@ public class ForumPostPanel extends Panel {
                 }
             }
         );
+
+        add(
+            new Link<>("delete", model) {
+                @Override
+                public void onClick() {
+                    ForumPost post = getModelObject();
+                    basicForumService.deletePost(post);
+                    onPostDeleted();
+                }
+
+                @Override
+                protected void onConfigure() {
+                    super.onConfigure();
+                    setVisible(allowDeletion() && basicForumService.canDelete(getModelObject()));
+                }
+            }
+        );
     }
+
+    protected boolean allowDeletion() {
+        return true;
+    }
+
+    protected void onPostDeleted() {}
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
index 9c62f77122..e0b29f4ec2 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
@@ -58,7 +58,16 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
             new ListView<>(POST_LIST, new PostProvider()) {
                 @Override
                 protected void populateItem(ListItem<ForumPost> item) {
-                    item.add(new ForumPostPanel(POST, item.getModel()));
+                    ListView<ForumPost> listView = this;
+                    item.add(
+                        new ForumPostPanel(POST, item.getModel()) {
+                            @Override
+                            protected void onPostDeleted() {
+                                // Refresh the list of posts
+                                listView.detach();
+                            }
+                        }
+                    );
                 }
             }
         );
diff --git a/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java b/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
index eb01e900c3..4c58b69967 100644
--- a/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
+++ b/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
@@ -22,7 +22,13 @@ interface Event {
 
         @Override
         public Component component(String id, IModel<Event> model) {
-            return new ForumPostPanel(id, model.map(Message.class::cast).map(m -> m.forumPost));
+            return new ForumPostPanel(id, model.map(Message.class::cast).map(m -> m.forumPost)) {
+                @Override
+                protected boolean allowDeletion() {
+                    // Do not allow deleting forum posts in the timeline
+                    return false;
+                }
+            };
         }
 
         @Override
-- 
2.39.5


From a9871eb470188dcb0440c84f824dbc6b4734c0ee Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Mon, 17 Feb 2025 14:02:12 +0100
Subject: [PATCH 3/5] Fix mock test

---
 .../java/se/su/dsv/scipro/forum/BasicForumServiceImplTest.java  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceImplTest.java b/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceImplTest.java
index de6288d6f4..6846f54411 100644
--- a/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceImplTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceImplTest.java
@@ -121,6 +121,8 @@ public class BasicForumServiceImplTest {
         ForumThread forumThread = new ForumThread();
         forumThread.addPost(post);
 
+        when(postRepository.findByThread(forumThread)).thenReturn(List.of(post));
+
         when(readStateRepository.find(eq(goodUser), isA(ForumPost.class))).thenReturn(readState);
         when(readStateRepository.find(eq(badUser), isA(ForumPost.class))).thenReturn(notReadState);
 
-- 
2.39.5


From fd8c7a620a9799d29641e7425f644c354d7bc976 Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Mon, 17 Feb 2025 14:17:39 +0100
Subject: [PATCH 4/5] Added tests for deletion logic

---
 .../BasicForumServiceIntegrationTest.java     | 83 +++++++++++++++++++
 .../se/su/dsv/scipro/test/SpringTest.java     | 27 +++++-
 2 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceIntegrationTest.java

diff --git a/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceIntegrationTest.java
new file mode 100644
index 0000000000..ec5d815af7
--- /dev/null
+++ b/core/src/test/java/se/su/dsv/scipro/forum/BasicForumServiceIntegrationTest.java
@@ -0,0 +1,83 @@
+package se.su.dsv.scipro.forum;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.inject.Inject;
+import java.util.Set;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import se.su.dsv.scipro.forum.dataobjects.ForumPost;
+import se.su.dsv.scipro.forum.dataobjects.ForumThread;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.test.IntegrationTest;
+
+public class BasicForumServiceIntegrationTest extends IntegrationTest {
+
+    @Inject
+    BasicForumService basicForumService;
+
+    private User op;
+    private User commenter;
+
+    @BeforeEach
+    public void setUp() {
+        User op = User.builder().firstName("Bill").lastName("Gates").emailAddress("bill@example.com").build();
+        this.op = save(op);
+
+        User commenter = User.builder().firstName("Steve").lastName("Jobs").emailAddress("steve@example.com").build();
+        this.commenter = save(commenter);
+    }
+
+    @Test
+    public void can_not_delete_original_post() {
+        ForumThread thread = basicForumService.createThread("Test thread");
+        ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
+
+        setLoggedInAs(op);
+
+        assertFalse(basicForumService.canDelete(originalPost));
+        assertThrows(IllegalArgumentException.class, () -> basicForumService.deletePost(originalPost));
+    }
+
+    @Test
+    public void can_delete_reply_to_original_post() {
+        ForumThread thread = basicForumService.createThread("Test thread");
+        ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
+        ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
+
+        setLoggedInAs(commenter);
+
+        assertTrue(basicForumService.canDelete(reply));
+        assertDoesNotThrow(() -> basicForumService.deletePost(reply));
+    }
+
+    @Test
+    public void can_not_delete_someone_elses_reply() {
+        ForumThread thread = basicForumService.createThread("Test thread");
+        ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
+        ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
+
+        setLoggedInAs(op);
+
+        assertFalse(basicForumService.canDelete(reply));
+        assertThrows(IllegalArgumentException.class, () -> basicForumService.deletePost(reply));
+    }
+
+    @Test
+    public void system_can_delete_all_replies() {
+        ForumThread thread = basicForumService.createThread("Test thread");
+        ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
+        ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
+        ForumPost secondReply = basicForumService.createReply(thread, op, "Test post", Set.of());
+
+        setLoggedInAs(null);
+
+        assertTrue(basicForumService.canDelete(reply));
+        assertDoesNotThrow(() -> basicForumService.deletePost(reply));
+        assertTrue(basicForumService.canDelete(secondReply));
+        assertDoesNotThrow(() -> basicForumService.deletePost(secondReply));
+    }
+}
diff --git a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java
index 04d0f70da7..287e49e710 100644
--- a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java
@@ -1,5 +1,6 @@
 package se.su.dsv.scipro.test;
 
+import jakarta.inject.Inject;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
 import jakarta.persistence.EntityTransaction;
@@ -25,6 +26,7 @@ import se.su.dsv.scipro.RepositoryConfiguration;
 import se.su.dsv.scipro.profiles.CurrentProfile;
 import se.su.dsv.scipro.sukat.Sukat;
 import se.su.dsv.scipro.system.CurrentUser;
+import se.su.dsv.scipro.system.User;
 
 @Testcontainers
 public abstract class SpringTest {
@@ -35,6 +37,9 @@ public abstract class SpringTest {
     @Container
     static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
 
+    @Inject
+    private TestUser testUser;
+
     @BeforeEach
     public final void prepareSpring() throws SQLException {
         MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl());
@@ -56,6 +61,8 @@ public abstract class SpringTest {
         annotationConfigApplicationContext.getBeanFactory().registerSingleton("entityManager", this.entityManager);
         annotationConfigApplicationContext.refresh();
         annotationConfigApplicationContext.getAutowireCapableBeanFactory().autowireBean(this);
+
+        testUser.setUser(null); // default to system
     }
 
     @AfterEach
@@ -75,6 +82,10 @@ public abstract class SpringTest {
         }
     }
 
+    protected void setLoggedInAs(User user) {
+        this.testUser.setUser(user);
+    }
+
     @Configuration(proxyBeanMethods = false)
     @Import({ CoreConfig.class, RepositoryConfiguration.class })
     public static class TestContext {
@@ -96,7 +107,7 @@ public abstract class SpringTest {
 
         @Bean
         public CurrentUser currentUser() {
-            return () -> null;
+            return new TestUser();
         }
 
         @Bean
@@ -106,4 +117,18 @@ public abstract class SpringTest {
             return currentProfile;
         }
     }
+
+    private static class TestUser implements CurrentUser {
+
+        private User user;
+
+        @Override
+        public User get() {
+            return user;
+        }
+
+        private void setUser(User user) {
+            this.user = user;
+        }
+    }
 }
-- 
2.39.5


From 46f7d50ed7310b814663280858c5134044997c9f Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Mon, 17 Mar 2025 10:51:28 +0100
Subject: [PATCH 5/5] Inverted logic, deletion is not allowed by default.

---
 .../dsv/scipro/forum/panels/threaded/ForumPostPanel.java  | 2 +-
 .../forum/panels/threaded/ViewForumThreadPanel.java       | 5 +++++
 .../java/se/su/dsv/scipro/reviewer/timeline/Event.java    | 8 +-------
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
index c86fbe18f6..225240d8e1 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ForumPostPanel.java
@@ -89,7 +89,7 @@ public class ForumPostPanel extends Panel {
     }
 
     protected boolean allowDeletion() {
-        return true;
+        return false;
     }
 
     protected void onPostDeleted() {}
diff --git a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
index e0b29f4ec2..06b89d7e14 100644
--- a/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/forum/panels/threaded/ViewForumThreadPanel.java
@@ -61,6 +61,11 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
                     ListView<ForumPost> listView = this;
                     item.add(
                         new ForumPostPanel(POST, item.getModel()) {
+                            @Override
+                            protected boolean allowDeletion() {
+                                return true;
+                            }
+
                             @Override
                             protected void onPostDeleted() {
                                 // Refresh the list of posts
diff --git a/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java b/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
index 4c58b69967..eb01e900c3 100644
--- a/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
+++ b/view/src/main/java/se/su/dsv/scipro/reviewer/timeline/Event.java
@@ -22,13 +22,7 @@ interface Event {
 
         @Override
         public Component component(String id, IModel<Event> model) {
-            return new ForumPostPanel(id, model.map(Message.class::cast).map(m -> m.forumPost)) {
-                @Override
-                protected boolean allowDeletion() {
-                    // Do not allow deleting forum posts in the timeline
-                    return false;
-                }
-            };
+            return new ForumPostPanel(id, model.map(Message.class::cast).map(m -> m.forumPost));
         }
 
         @Override
-- 
2.39.5