WIP: Allow deletion of entire forum threads #111
core/src
main/java/se/su/dsv/scipro/forum
BasicForumService.javaBasicForumServiceImpl.javaGroupForumService.javaGroupForumServiceImpl.javaProjectForumService.javaProjectForumServiceImpl.java
dataobjects
test/java/se/su/dsv/scipro/forum
view/src
main/java/se/su/dsv/scipro
test/java/se/su/dsv/scipro/test
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user