WIP: Allow deletion of entire forum threads #111

Draft
ansv7779 wants to merge 10 commits from remove-forum-post into develop
23 changed files with 465 additions and 273 deletions

@ -22,6 +22,8 @@ public interface BasicForumService extends Serializable {
ForumThread createThread(String subject);
ForumThread findThreadById(Long id);
long countUnreadThreads(List<ForumThread> forumThreadList, User user);
ForumPost getLastPost(ForumThread forumThread);
@ -31,4 +33,8 @@ public interface BasicForumService extends Serializable {
boolean canDelete(ForumPost forumPost);
void deletePost(ForumPost post);
boolean canDelete(ForumThread forumThread);
void deleteThread(ForumThread forumThread);
}

@ -91,6 +91,11 @@ public class BasicForumServiceImpl implements BasicForumService {
return threadRepository.save(forumThread);
}
@Override
public ForumThread findThreadById(Long id) {
return threadRepository.findOne(id);
}
@Override
public long countUnreadThreads(List<ForumThread> forumThreadList, User user) {
return postRepository.countUnreadThreads(forumThreadList, user);
@ -179,8 +184,35 @@ public class BasicForumServiceImpl implements BasicForumService {
if (!canDelete(post)) {
throw new PostCantBeDeletedException();
}
post.setDeleted(true);
postRepository.save(post);
ForumThread forumThread = post.getForumThread();
forumThread.getPosts().remove(post);
threadRepository.save(forumThread);
}
@Override
public boolean canDelete(ForumThread forumThread) {
boolean hasReplies = getPosts(forumThread).size() > 1;
if (hasReplies) {
return false;
}
User currentUser = currentUserProvider.get();
if (currentUser == null) {
// Allow the system to delete any thread
return true;
}
return Objects.equals(currentUser, forumThread.getCreatedBy());
}
@Override
@Transactional
public void deleteThread(ForumThread forumThread) {
if (!canDelete(forumThread)) {
throw new IllegalArgumentException("Not allowed to delete thread");
}
threadRepository.delete(forumThread);
}
private static final class PostCantBeDeletedException extends IllegalArgumentException {

@ -18,4 +18,12 @@ public interface GroupForumService {
ForumPost createReply(GroupThread groupThread, User poster, String content, Set<Attachment> attachments);
List<ForumPost> getPosts(GroupThread groupThread);
boolean canDelete(GroupThread groupThread, ForumPost post);
void deletePost(GroupThread groupThread, ForumPost post);
boolean canDeleteThread(GroupThread groupThread);
void deleteThread(GroupThread groupThread);
}

@ -81,4 +81,25 @@ public class GroupForumServiceImpl implements GroupForumService {
public List<ForumPost> getPosts(final GroupThread groupThread) {
return basicForumService.getPosts(groupThread.getForumThread());
}
@Override
public boolean canDelete(GroupThread groupThread, ForumPost post) {
return basicForumService.canDelete(post);
}
@Override
public void deletePost(GroupThread groupThread, ForumPost post) {
basicForumService.deletePost(post);
}
@Override
public boolean canDeleteThread(GroupThread groupThread) {
return basicForumService.canDelete(groupThread.getForumThread());
}
@Override
public void deleteThread(GroupThread groupThread) {
groupThreadRepository.delete(groupThread);
basicForumService.deleteThread(groupThread.getForumThread());
}
}

@ -24,4 +24,12 @@ public interface ProjectForumService {
List<Pair<ProjectThread, ForumPost>> latestPost(Project a, int amount);
long getUnreadThreadsCount(Project project, User user);
boolean canDelete(ProjectThread projectThread, ForumPost post);
void deletePost(ProjectThread projectThread, ForumPost post);
boolean canDelete(ProjectThread projectThread);
void deleteThread(ProjectThread projectThread);
}

@ -121,6 +121,28 @@ public class ProjectForumServiceImpl implements ProjectForumService {
return basicForumService.countUnreadThreads(list, user);
}
@Override
public boolean canDelete(ProjectThread projectThread, ForumPost post) {
return basicForumService.canDelete(post);
}
@Override
public void deletePost(ProjectThread projectThread, ForumPost post) {
basicForumService.deletePost(post);
}
@Override
public boolean canDelete(ProjectThread projectThread) {
return basicForumService.canDelete(projectThread.getForumThread());
}
@Override
@Transactional
public void deleteThread(ProjectThread projectThread) {
projectThreadRepository.delete(projectThread);
basicForumService.deleteThread(projectThread.getForumThread());
}
@Override
public ProjectThread findOne(long threadId) {
return projectThreadRepository.findOne(threadId);

@ -11,11 +11,11 @@ import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import se.su.dsv.scipro.system.LazyDeletableDomainObject;
import se.su.dsv.scipro.system.User;
@ -38,17 +38,9 @@ public class ForumThread extends LazyDeletableDomainObject {
// ----------------------------------------------------------------------------------
// JPA-mappings of other tables referencing to this table "thread"
// ----------------------------------------------------------------------------------
@OneToMany(mappedBy = "forumThread", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@OneToMany(mappedBy = "forumThread", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<ForumPost> posts = new ArrayList<>();
// ----------------------------------------------------------------------------------
// JPA-lifecycle method
// ----------------------------------------------------------------------------------
@PostLoad
void lazyDeletion() {
posts.removeIf(LazyDeletableDomainObject::isDeleted);
}
// ----------------------------------------------------------------------------------
// Properties (Getters and Setters)
// ----------------------------------------------------------------------------------
@ -109,8 +101,8 @@ public class ForumThread extends LazyDeletableDomainObject {
posts.add(post);
}
public int getPostCount() {
return posts.size();
public long getPostCount() {
return posts.stream().filter(Predicate.not(ForumPost::isDeleted)).count();
}
public User getCreatedBy() {

@ -1,198 +0,0 @@
package se.su.dsv.scipro.forum;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import com.google.common.eventbus.EventBus;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
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.User;
import se.su.dsv.scipro.test.ForumBuilder;
import se.su.dsv.scipro.test.UserBuilder;
@ExtendWith(MockitoExtension.class)
public class BasicForumServiceImplTest {
@Mock
private AbstractThreadRepository threadRepository;
@Mock
private ForumPostReadStateRepository readStateRepository;
@Mock
private ForumPostRepository postRepository;
@Mock
private EventBus eventBus;
@InjectMocks
private BasicForumServiceImpl basicForumService;
@Test
public void testGetPostPageByForumThread() {
List<ForumPost> posts = Collections.singletonList(new ForumBuilder().createPost());
when(postRepository.findByThread(isA(ForumThread.class))).thenReturn(posts);
List<ForumPost> servicePage = basicForumService.getPosts(mock(ForumThread.class));
assertEquals(posts, servicePage);
}
@Test
public void testMarkRead() {
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
User user = new User();
ForumPost post = new ForumPost();
boolean read = basicForumService.setRead(user, post, true);
assertTrue(read, "Did not return proper read state");
ArgumentCaptor<ForumPostReadState> captor = ArgumentCaptor.forClass(ForumPostReadState.class);
verify(readStateRepository, times(1)).save(captor.capture());
assertTrue(captor.getValue().isRead(), "Did not save correct read state");
}
@Test
public void testMarkUnread() {
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
// when
User user = new User();
ForumPost post = new ForumPost();
// when
boolean read = basicForumService.setRead(user, post, false);
// then
assertFalse(read, "Did not return proper read state");
ArgumentCaptor<ForumPostReadState> captor = ArgumentCaptor.forClass(ForumPostReadState.class);
verify(readStateRepository, times(1)).save(captor.capture());
assertFalse(captor.getValue().isRead(), "Did not save correct read state");
}
@Test
public void testMarkThreadReadPostsEvent() {
User user = new User();
ForumPost post = new ForumPost();
post.setContent("post 1");
ForumPost post2 = new ForumPost();
post2.setContent("post 2");
ForumThread forumThread = new ForumThread();
forumThread.addPost(post);
forumThread.addPost(post2);
basicForumService.setThreadRead(user, forumThread, true);
verify(eventBus).post(new ForumPostReadEvent(post, user));
verify(eventBus).post(new ForumPostReadEvent(post2, user));
}
@Test
public void testIsThreadRead() {
User goodUser = new UserBuilder().setFirstName("Reads").setLastName("Forum").create();
User badUser = new UserBuilder().setFirstName("Does not read").setLastName("Forum").create();
ForumPost post = new ForumPost();
ForumPostReadState readState = new ForumPostReadState(goodUser, post);
readState.setRead(true);
ForumPostReadState notReadState = new ForumPostReadState(badUser, post);
notReadState.setRead(false);
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);
boolean goodUserState = basicForumService.isThreadRead(goodUser, forumThread);
boolean badUserState = basicForumService.isThreadRead(badUser, forumThread);
assertTrue(goodUserState, "Good user has not read all thread posts");
assertFalse(badUserState, "Bad user has read all thread posts");
}
@Test
public void create_forum_thread() {
when(threadRepository.save(any(ForumThread.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
final String subject = "Subject";
ForumThread thread = basicForumService.createThread(subject);
assertThat(thread.getSubject(), is(subject));
}
@Test
public void reply_to_thread() {
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
when(threadRepository.save(any(ForumThread.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
when(postRepository.save(any(ForumPost.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
final ForumThread forumThread = new ForumThread();
final User poster = User.builder().firstName("Bob").lastName("Example").emailAddress("bob@example.com").build();
final String content = "content";
ForumPost forumPost = basicForumService.createReply(forumThread, poster, content, Collections.emptySet());
assertThat(forumPost.getContent(), is(content));
assertThat(forumPost.getPostedBy(), is(poster));
assertThat(forumPost.getForumThread(), is(forumThread));
}
@Test
public void mark_post_read_posts_event() {
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
final ForumPost post = new ForumPost();
post.setId(235235L);
final User user = new User();
user.setId(2378924L);
basicForumService.setRead(user, post, true);
ArgumentCaptor<ForumPostReadEvent> captor = ArgumentCaptor.forClass(ForumPostReadEvent.class);
verify(eventBus).post(captor.capture());
ForumPostReadEvent event = captor.getValue();
assertEquals(event.post(), post);
assertEquals(event.user(), user);
}
@Test
public void mark_post_unread_does_not_post_read_event() {
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
final ForumPost post = new ForumPost();
post.setId(235235L);
final User user = new User();
user.setId(2378924L);
basicForumService.setRead(user, post, false);
verify(eventBus, never()).post(isA(ForumPostReadEvent.class));
}
}

@ -1,11 +1,17 @@
package se.su.dsv.scipro.forum;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -80,4 +86,157 @@ public class BasicForumServiceIntegrationTest extends IntegrationTest {
assertTrue(basicForumService.canDelete(secondReply));
assertDoesNotThrow(() -> basicForumService.deletePost(secondReply));
}
@Test
public void testGetPostPageByForumThread() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post1 = basicForumService.createReply(thread, op, "Test post 1", Set.of());
ForumPost post2 = basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
List<ForumPost> posts = basicForumService.getPosts(thread);
assertThat(posts, contains(post1, post2));
}
@Test
public void testMarkRead() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
boolean read = basicForumService.setRead(commenter, post, true);
assertTrue(read, "Did not return proper read state");
boolean isRead = basicForumService.isRead(commenter, post);
assertTrue(isRead, "Did not save correct read state");
}
@Test
public void testMarkUnread() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
boolean read = basicForumService.setRead(op, post, false);
assertFalse(read, "Did not return proper read state");
boolean isRead = basicForumService.isRead(commenter, post);
assertFalse(isRead, "Did not save correct read state");
}
@Test
public void testMarkThreadReadPostsEvent() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post1 = basicForumService.createReply(thread, op, "Test post 1", Set.of());
ForumPost post2 = basicForumService.createReply(thread, op, "Test post 2", Set.of());
basicForumService.setThreadRead(commenter, thread, true);
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post1, commenter)));
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post2, commenter)));
}
@Test
public void testIsThreadRead() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
basicForumService.setThreadRead(commenter, thread, true);
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
boolean goodUserState = basicForumService.isThreadRead(commenter, thread);
boolean badUserState = basicForumService.isThreadRead(op, thread);
assertTrue(goodUserState, "Good user has not read all thread posts");
assertFalse(badUserState, "Bad user has read all thread posts");
}
@Test
public void mark_post_read_posts_event() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
basicForumService.setRead(commenter, post, true);
assertThat(getPublishedEvents(), hasItem(new ForumPostReadEvent(post, commenter)));
}
@Test
public void mark_post_unread_does_not_post_read_event() {
ForumThread thread = basicForumService.createThread("Test thread");
ForumPost post = basicForumService.createReply(thread, op, "Test post 1", Set.of());
basicForumService.setRead(commenter, post, false);
assertThat(getPublishedEvents(), not(hasItem(new ForumPostReadEvent(post, commenter))));
}
@Test
public void can_delete_own_thread_without_replies() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
setLoggedInAs(op);
assertTrue(basicForumService.canDelete(thread));
}
@Test
public void cant_delete_others_threads_without_replies() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
setLoggedInAs(commenter);
assertFalse(basicForumService.canDelete(thread));
}
@Test
public void system_can_delete_all_threads() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
setLoggedInAs(null);
assertTrue(basicForumService.canDelete(thread));
}
@Test
public void can_not_delete_thread_with_replies() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
setLoggedInAs(op);
assertFalse(basicForumService.canDelete(thread));
}
@Test
public void deleting_thread_removes_it() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
setLoggedInAs(op);
assertTrue(basicForumService.canDelete(thread));
assertDoesNotThrow(() -> basicForumService.deleteThread(thread));
ForumThread threadById = basicForumService.findThreadById(thread.getId());
assertNull(threadById, "Thread still exists");
}
@Test
public void trying_to_delete_thread_with_replies_throws() {
ForumThread thread = basicForumService.createThread("Test thread");
basicForumService.createReply(thread, op, "Test post 1", Set.of());
basicForumService.createReply(thread, commenter, "Test post 2", Set.of());
setLoggedInAs(op);
assertThrows(IllegalArgumentException.class, () -> basicForumService.deleteThread(thread));
}
}

@ -6,8 +6,23 @@ import se.su.dsv.scipro.forum.dataobjects.ForumPost;
import se.su.dsv.scipro.system.User;
public interface ForumThread<A> extends IClusterable {
List<ForumPost> getPosts(A a);
/**
* @param a the context of the thread
* @return the initial post of the thread
*/
Optional<ForumPost> getInitialPost(A a);
List<ForumPost> getReplies(A a);
ForumPost reply(A a, User poster, String content, Set<Attachment> attachments);
String getSubject(A a);
void setRead(User user, A a);
boolean canDelete(A a, ForumPost post);
void deletePost(A a, ForumPost post);
boolean canDeleteThread(A a);
void deleteThread(A a);
}

@ -72,6 +72,14 @@ public class SupervisorViewForumThreadPage
new ProjectForumThread(projectForumService),
projectModel
) {
@Override
protected void onThreadDeleted() {
setResponsePage(
SupervisorForumBasePage.class,
SupervisorForumBasePage.getPageParameters(projectModel.getObject())
);
}
@Override
protected Class<? extends Page> getForumPage() {
return SupervisorForumBasePage.class;

@ -5,12 +5,12 @@
<title></title>
</head>
<body>
<wicket:panel>
<wicket:border>
<div class="messageWrap">
<div class="forumBlueBackground d-flex justify-content-between">
<!-- DATE ROW-->
<span wicket:id="dateCreated"></span>
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
<wicket:body/>
</div>
<div class="forumGrayBackground">
<div class="vertAlign">
@ -29,6 +29,6 @@
</wicket:enclosure>
</div>
</div>
</wicket:panel>
</wicket:border>
</body>
</html>

@ -1,10 +1,8 @@
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.markup.html.border.Border;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LambdaModel;
import se.su.dsv.scipro.components.DateLabel;
@ -13,26 +11,23 @@ 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 {
public class ForumPostPanel extends Border {
public static final String POSTED_BY = "postedBy";
public static final String DATE_CREATED = "dateCreated";
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)));
add(
super(id, model);
addToBorder(
new UserLinkPanel(POSTED_BY, LambdaModel.of(model, ForumPost::getPostedBy, ForumPost::setPostedBy))
);
addToBorder(
new WebMarkupContainer("postedBySystem") {
@Override
protected void onConfigure() {
@ -41,18 +36,18 @@ public class ForumPostPanel extends Panel {
}
}
);
add(
addToBorder(
new DateLabel(
DATE_CREATED,
LambdaModel.of(model, ForumPost::getDateCreated, ForumPost::setDateCreated),
DateStyle.DATETIME
)
);
add(
addToBorder(
new SmarterLinkMultiLineLabel(CONTENT, LambdaModel.of(model, ForumPost::getContent, ForumPost::setContent))
);
add(
addToBorder(
new DisplayMultiplesPanel<>(
ATTACHMENT,
new ListAdapterModel<>(LambdaModel.of(model, ForumPost::getAttachments, ForumPost::setAttachments))
@ -69,28 +64,5 @@ 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 false;
}
protected void onPostDeleted() {}
}

@ -17,8 +17,17 @@ public class ProjectForumThread implements ForumThread<ProjectThread> {
}
@Override
public List<ForumPost> getPosts(final ProjectThread projectThread) {
return projectForumService.getPosts(projectThread);
public Optional<ForumPost> getInitialPost(ProjectThread projectThread) {
return projectForumService.getPosts(projectThread).stream().findFirst();
}
@Override
public List<ForumPost> getReplies(final ProjectThread projectThread) {
List<ForumPost> posts = projectForumService.getPosts(projectThread);
if (posts.size() <= 1) {
return List.of();
}
return posts.subList(1, posts.size());
}
@Override
@ -40,4 +49,24 @@ public class ProjectForumThread implements ForumThread<ProjectThread> {
public void setRead(User user, ProjectThread thread) {
projectForumService.markRead(user, thread);
}
@Override
public boolean canDelete(ProjectThread projectThread, ForumPost post) {
return projectForumService.canDelete(projectThread, post);
}
@Override
public void deletePost(ProjectThread projectThread, ForumPost post) {
projectForumService.deletePost(projectThread, post);
}
@Override
public boolean canDeleteThread(ProjectThread projectThread) {
return projectForumService.canDelete(projectThread);
}
@Override
public void deleteThread(ProjectThread projectThread) {
projectForumService.deleteThread(projectThread);
}
}

@ -19,8 +19,13 @@
<div wicket:id="topReplyPanel"></div>
<!-- POST LIST-->
<div class="d-flex flex-column-reverse">
<wicket:container wicket:id="initialPost">
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
</wicket:container>
<wicket:container wicket:id="postList">
<wicket:container wicket:id="post"/>
<wicket:container wicket:id="post">
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
</wicket:container>
</wicket:container>
</div>
</div>

@ -1,5 +1,6 @@
package se.su.dsv.scipro.forum.panels.threaded;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.apache.wicket.Component;
@ -7,11 +8,13 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import se.su.dsv.scipro.components.OrNullModel;
import se.su.dsv.scipro.forum.ForumThread;
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
import se.su.dsv.scipro.session.SciProSession;
@ -20,10 +23,9 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
private final ForumThread<A> forumThread;
private final WebMarkupContainer wrapper;
private SubmitForumReplyPanel topReplyPanel;
private SubmitForumReplyPanel<A> topReplyPanel;
private AjaxLink<Void> topReplyLink;
@SuppressWarnings("unchecked")
public ViewForumThreadPanel(String id, final IModel<A> model, final ForumThread<A> forumThread) {
super(id, model);
this.forumThread = forumThread;
@ -54,30 +56,66 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
}
private void addPostList() {
ForumPostPanel initialPostPanel = new ForumPostPanel(
"initialPost",
new OrNullModel<>(getModel().map(forumThread::getInitialPost))
) {
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(getDefaultModelObject() != null);
}
};
initialPostPanel.add(
new Link<>("delete", getModel()) {
@Override
public void onClick() {
forumThread.deleteThread(getModelObject());
onThreadDeleted();
}
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(forumThread.canDeleteThread(getModelObject()));
}
}
);
wrapper.add(initialPostPanel);
wrapper.add(
new ListView<>(POST_LIST, new PostProvider()) {
@Override
protected void populateItem(ListItem<ForumPost> item) {
ListView<ForumPost> listView = this;
item.add(
new ForumPostPanel(POST, item.getModel()) {
ForumPostPanel forumPostPanel = new ForumPostPanel(POST, item.getModel());
forumPostPanel.add(
new Link<>("delete", item.getModel()) {
@Override
protected boolean allowDeletion() {
return true;
public void onClick() {
ForumPost post = getModelObject();
forumThread.deletePost(ViewForumThreadPanel.this.getModelObject(), post);
listView.detach();
}
@Override
protected void onPostDeleted() {
// Refresh the list of posts
listView.detach();
protected void onConfigure() {
super.onConfigure();
setVisible(
forumThread.canDelete(ViewForumThreadPanel.this.getModelObject(), getModelObject())
);
}
}
);
item.add(forumPostPanel);
}
}
);
}
protected void onThreadDeleted() {
// callback when a thread is deleted
}
private void addReplyButtons() {
topReplyLink = new AjaxLink<>(TOP_REPLY_LINK) {
@Override
@ -118,9 +156,10 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
@Override
protected List<ForumPost> load() {
List<ForumPost> posts = forumThread.getPosts(getModelObject());
posts.sort(Comparator.comparing(ForumPost::getDateCreated));
return posts;
List<ForumPost> posts = forumThread.getReplies(getModelObject());
ArrayList<ForumPost> sortedPosts = new ArrayList<>(posts);
sortedPosts.sort(Comparator.comparing(ForumPost::getDateCreated));
return sortedPosts;
}
}
}

@ -29,6 +29,11 @@ public class ViewProjectForumThreadPanel extends ViewForumThreadPanel<ProjectThr
return new BookmarkablePageLink<Void>(id, getForumPage(), getProjectParameters(projectModel.getObject()));
}
@Override
protected void onThreadDeleted() {
setResponsePage(getForumPage(), getProjectParameters(projectModel.getObject()));
}
protected Class<? extends Page> getForumPage() {
return ProjectForumBasePage.class;
}

@ -17,8 +17,17 @@ public class GroupForumThread implements ForumThread<GroupThread> {
}
@Override
public List<ForumPost> getPosts(final GroupThread groupThread) {
return groupForumService.getPosts(groupThread);
public Optional<ForumPost> getInitialPost(GroupThread groupThread) {
return groupForumService.getPosts(groupThread).stream().findFirst();
}
@Override
public List<ForumPost> getReplies(final GroupThread groupThread) {
List<ForumPost> posts = groupForumService.getPosts(groupThread);
if (posts.size() <= 1) {
return List.of();
}
return posts.subList(1, posts.size());
}
@Override
@ -40,4 +49,24 @@ public class GroupForumThread implements ForumThread<GroupThread> {
public void setRead(User user, GroupThread groupThread) {
groupForumService.markRead(user, groupThread);
}
@Override
public boolean canDelete(GroupThread groupThread, ForumPost post) {
return groupForumService.canDelete(groupThread, post);
}
@Override
public void deletePost(GroupThread groupThread, ForumPost post) {
groupForumService.deletePost(groupThread, post);
}
@Override
public boolean canDeleteThread(GroupThread groupThread) {
return groupForumService.canDeleteThread(groupThread);
}
@Override
public void deleteThread(GroupThread groupThread) {
groupForumService.deleteThread(groupThread);
}
}

@ -56,6 +56,13 @@ public class ViewThreadPage extends AbstractAuthorGroupPage implements MenuHighl
);
return new BookmarkablePageLink<Void>(id, AuthorGroupPage.class, pageParameters);
}
@Override
protected void onThreadDeleted() {
PageParameters pageParameters = new PageParameters();
pageParameters.set(PageParameterKeys.MAP.get(Group.class), getGroup().getObject().getId());
setResponsePage(AuthorGroupPage.class, pageParameters);
}
}
);
}

@ -18,7 +18,12 @@ public class ReviewerInteractionForumThread implements ForumThread<Project>, ICl
}
@Override
public List<ForumPost> getPosts(final Project project) {
public Optional<ForumPost> getInitialPost(Project project) {
return Optional.empty();
}
@Override
public List<ForumPost> getReplies(final Project project) {
return reviewerInteractionService.getPosts(project);
}
@ -41,4 +46,24 @@ public class ReviewerInteractionForumThread implements ForumThread<Project>, ICl
public void setRead(User user, Project project) {
reviewerInteractionService.markAllRead(user, project);
}
@Override
public boolean canDelete(Project project, ForumPost post) {
return false;
}
@Override
public void deletePost(Project project, ForumPost post) {
throw new UnsupportedOperationException("Can not delete posts in reviewer interaction");
}
@Override
public boolean canDeleteThread(Project project) {
return false;
}
@Override
public void deleteThread(Project project) {
throw new UnsupportedOperationException("Can not delete the entire reviewer interaction");
}
}

@ -8,7 +8,7 @@
<wicket:panel>
<div class="d-flex flex-column-reverse pt-1">
<wicket:container wicket:id="events">
<wicket:container wicket:id="event"/>
<wicket:container wicket:id="event"></wicket:container>
</wicket:container>
</div>
</wicket:panel>

@ -49,6 +49,14 @@ public class SupervisorViewGroupThreadPage
);
return new BookmarkablePageLink<Void>(id, SupervisorGroupPage.class, pageParameters);
}
@Override
protected void onThreadDeleted() {
setResponsePage(
SupervisorGroupPage.class,
SupervisorGroupPage.getPageParameters(groupModel.getObject())
);
}
}
);
}