/** * Find last read post in the list for given topic. * * @param lastReadPosts the list of last read posts where we are going to search * @param topicId an identifier of topic for which we find last read post in list * @return last read post for given topic */ private LastReadPost findLastReadPost(List<LastReadPost> lastReadPosts, long topicId) { for (LastReadPost lastReadPost : lastReadPosts) { if (lastReadPost.getTopic().getId() == topicId) { return lastReadPost; } } return null; }
/** * {@inheritDoc} */ @Override public <T extends SubscriptionAwareEntity> String getUnsubscribeLinkForSubscribersOf(Class<T> clazz) { if (Branch.class.isAssignableFrom(clazz)) { return getBranch().getUnsubscribeLinkForSubscribersOf(clazz); } else { //In case of post unsubscribe from topic too return String.format("/topics/%s/unsubscribe", getId()); } }
/** * Makes URL to mark topic page as read. * For anonymous user returns empty optional. * * @param user current user * @param page page to mark as read * @return Optional url string */ public Optional<String> getMarkAsReadUrl(JCUser user, String page) { if (user.isAnonymous()) { return Optional.absent(); } return Optional.of("{topicId}/page/{pageNum}/markread?userId={userId}&lastModified={lastModified}" .replace("{topicId}", String.valueOf(getId())) .replace("{pageNum}", page) .replace("{userId}", String.valueOf(user.getId())) .replace("{lastModified}", String.valueOf(getLastModificationPostDate().getMillis())) ); } }
/** * {@inheritDoc} */ @PreAuthorize("(hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.DELETE_OWN_POSTS') and " + "#topic.containsOwnerPostsOnly and #topic.topicStarter.id == principal.id) or " + "(hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.DELETE_OTHERS_POSTS') and " + "hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.DELETE_OWN_POSTS'))") @Override public void deleteTopic(Topic topic) throws NotFoundException { deleteTopicSilent(topic); notificationService.sendNotificationAboutRemovingTopic(topic); logger.info("Deleted topic \"{}\". Topic id: {}", topic.getTitle(), topic.getId()); }
/** * Update existing post * * @param postDto Dto populated in form * @param result validation result * @param postId the current postId * @return {@code ModelAndView} object which will be redirect to topic page * if saved successfully or show form with error message * @throws NotFoundException when topic, branch or post not found */ @RequestMapping(value = "/posts/{postId}/edit", method = RequestMethod.POST) public ModelAndView update(@Valid @ModelAttribute PostDto postDto, BindingResult result, @PathVariable(POST_ID) Long postId) throws NotFoundException { Post post = postService.get(postId); if (result.hasErrors()) { return new ModelAndView("topic/editPost") .addObject(TOPIC_ID, post.getTopic().getId()) .addObject(POST_ID, postId); } postService.updatePost(post, postDto.getBodyText()); return new ModelAndView("redirect:/posts/" + postId); }
/** * Edit post page filled with data from post with given id * * @param postId post id * @return redirect to post form page * @throws NotFoundException when topic or post not found */ @RequestMapping(value = "/posts/{postId}/edit", method = RequestMethod.GET) public ModelAndView editPage(@PathVariable(POST_ID) Long postId) throws NotFoundException { Post post = postService.get(postId); return new ModelAndView("topic/editPost") .addObject(POST_DTO, PostDto.getDtoFor(post)) .addObject(TOPIC_ID, post.getTopic().getId()) .addObject(POST_ID, postId) .addObject(TOPIC_TITLE, post.getTopic().getTitle()) .addObject("breadcrumbList", breadcrumbBuilder.getForumBreadcrumb(post.getTopic())); }
/** * Closes question with specified id. * * @param id id of question to close * * @return redirect to closed topic * @throws NotFoundException if question with specified id not found */ @RequestMapping(value = "{id}/close", method = RequestMethod.GET) public String closeQuestion(@PathVariable("id") Long id) throws NotFoundException { Topic topic = getTypeAwarePluginTopicService().get(id, QuestionsAndAnswersPlugin.TOPIC_TYPE); getTypeAwarePluginTopicService().closeTopic(topic); return "redirect:" + QuestionsAndAnswersPlugin.CONTEXT + "/" + topic.getId(); }
/** * Opens question with specified id. * * @param id of question to open * * @return redirect to opened question * @throws NotFoundException if question with specified if not found */ @RequestMapping(value = "{id}/open", method = RequestMethod.GET) public String openQuestion(@PathVariable("id") Long id) throws NotFoundException { Topic topic = getTypeAwarePluginTopicService().get(id, QuestionsAndAnswersPlugin.TOPIC_TYPE); getTypeAwarePluginTopicService().openTopic(topic); return "redirect:" + QuestionsAndAnswersPlugin.CONTEXT + "/" + topic.getId(); }
/** * {@inheritDoc} */ @Override public Branch deleteAllTopics(long branchId) throws NotFoundException { Branch branch = get(branchId); // Create tmp list to avoid ConcurrentModificationException List<Topic> loopList = new ArrayList<>(branch.getTopics()); for (Topic topic : loopList) { topicService.deleteTopicSilent(topic.getId()); } logger.info("All topics for branch \"{}\" were deleted. " + "Branch id: {}", branch.getName(), branch.getId()); return branch; }
/** * Fill the post breadcrumb. * * @param post {@link org.jtalks.jcommune.model.entity.Post} the breadcrumbed post. * @return {@link Breadcrumb} the filled breadcrumb for the Post location. */ private Breadcrumb preparePostBreadcrumb(Post post) { Topic topic = post.getTopic(); return new Breadcrumb( topic.getId(), BreadcrumbLocation.TOPIC, topic.getTitle()); } }
/** * For topics modified since forum was marked as all read we need to calculate * last read posts from data that were saved in repository. * * @param modifiedTopics the list of modified topics * @param currentUser the current user of application */ private void fillLastReadPostsForModifiedTopics(List<Topic> modifiedTopics, JCUser currentUser) { List<LastReadPost> lastReadPosts = lastReadPostDao.getLastReadPosts(currentUser, modifiedTopics); for (Topic topic : modifiedTopics) { LastReadPost lastReadPost = findLastReadPost(lastReadPosts, topic.getId()); if (lastReadPost != null) { topic.setLastReadPostDate(lastReadPost.getPostCreationDate()); } } }
/** * Populate topicUrl, readIconUrl, unreadIconUrl parameters of dto for topics provided by plugins * * @param plugableTopicDto dto to be populated * @param plugin plugin which provides this topic * * @return populated dto */ private TopicDto populatePlugableTopicDto(TopicDto plugableTopicDto, TopicPlugin plugin) { plugableTopicDto.setTopicUrl(PREFIX + plugin.getTopicType().toLowerCase() + "/" + plugableTopicDto.getTopic().getId()); plugableTopicDto.setPostUrlPrefix(PREFIX + plugin.getTopicType().toLowerCase() + "/" + plugableTopicDto.getTopic().getId() + "/post/"); if (plugableTopicDto.getTopic().isClosed()) { plugableTopicDto.setReadIconUrl(PREFIX + plugin.getTopicType().toLowerCase() + PLUGABLE_CLOSED_READ); plugableTopicDto.setUnreadIconUrl(PREFIX + plugin.getTopicType().toLowerCase() + PLUGABLE_CLOSED_UNREAD); } else { plugableTopicDto.setReadIconUrl(PREFIX + plugin.getTopicType().toLowerCase() + PLUGABLE_READ); plugableTopicDto.setUnreadIconUrl(PREFIX + plugin.getTopicType().toLowerCase() + PLUGABLE_UNREAD); } return plugableTopicDto; }
/** * Deletes answer by given id * * @param answerId id of the answer * @return redirect to question page * @throws NotFoundException when answer was not found */ @RequestMapping(method = RequestMethod.DELETE, value = "post/{answerId}") public String deleteAnswer(@PathVariable Long answerId) throws NotFoundException { Post answer = getPluginPostService().get(answerId); Post neighborAnswer = answer.getTopic().getNeighborPost(answer); getPluginPostService().deletePost(answer); return "redirect:" + QuestionsAndAnswersPlugin.CONTEXT + "/" + answer.getTopic().getId() + "#" + neighborAnswer.getId(); }
@Override @PreAuthorize("hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.CREATE_POSTS')") public PostDraft saveOrUpdateDraft(Topic topic, String content) { JCUser currentUser = userService.getCurrentUser(); PostDraft draft = topic.getDraftForUser(currentUser); if (draft == null) { draft = new PostDraft(content, currentUser); topic.addDraft(draft); } else { draft.setContent(content); draft.updateLastSavedTime(); } topicDao.saveOrUpdate(topic); logger.debug("Draft saved in topic. Topic id={}, Post id={}, Post author={}", new Object[]{topic.getId(), draft.getId(), currentUser.getUsername()}); return draft; }
/** * {@inheritDoc} */ @PreAuthorize("hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.MOVE_TOPICS')") @Override public void moveTopic(Topic topic, Long branchId) throws NotFoundException { Branch sourceBranch = topic.getBranch(); Branch targetBranch = branchDao.get(branchId); if (targetBranch == null) { throw new NotFoundException("Target branch not exist"); } targetBranch.addTopic(topic); branchDao.saveOrUpdate(targetBranch); List<Post> topicPosts = topic.getPosts(); if (topicPosts.contains(sourceBranch.getLastPost())) { branchLastPostService.refreshLastPostInBranch(sourceBranch); } branchLastPostService.refreshLastPostInBranch(targetBranch); notificationService.sendNotificationAboutTopicMoved(topic); logger.info("Moved topic \"{}\". Topic id: {}", topic.getTitle(), topic.getId()); }
/** * {@inheritDoc} * * @throws AccessDeniedException besides other reasons, always throws this when Code Review is edited because it * shouldn't be possible to edit it. More details on requirements can be found here * <a href="http://jtalks.org/display/jcommune/1.1+Larks">here</a>. */ @Override @PreAuthorize("(hasPermission(#topic.id, 'TOPIC', 'GeneralPermission.WRITE') and " + "hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.EDIT_OWN_POSTS')) or " + "(not hasPermission(#topic.id, 'TOPIC', 'GeneralPermission.WRITE') and " + "hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.EDIT_OTHERS_POSTS'))") public void updateTopic(Topic topic, Poll poll) { if (topic.isCodeReview()) { throw new AccessDeniedException("It is not allowed to edit Code Review!"); } Post post = topic.getFirstPost(); post.updateModificationDate(); if (poll != null && poll.getEndingDate() != null) { topic.getPoll().setEndingDate(poll.getEndingDate()); } dao.saveOrUpdate(topic); logger.debug("Topic id={} updated", topic.getId()); }
/** * Fills this dto from draft * * @param draft draft to fill dto */ public void fillFrom(PostDraft draft) { this.setBodyText(draft.getContent()); this.setId(draft.getId()); this.setTopicId(draft.getTopic().getId()); this.setCreationDate(draft.getLastSaved()); } }
/** * Creates dto from draft * * @param postdraft draft to create dto * * @return dto for draft */ public static PostDto getDtoFor(PostDraft postdraft) { PostDto dto = new PostDto(); dto.setBodyText(postdraft.getContent()); dto.setId(postdraft.getId()); dto.setTopicId(postdraft.getTopic().getId()); dto.setCreationDate(postdraft.getLastSaved()); return dto; }
/** * Create dto * * @param post post for conversion * @return dto for post */ public static PostDto getDtoFor(Post post) { PostDto dto = new PostDto(); dto.setBodyText(post.getPostContent()); dto.setId(post.getId()); dto.setTopicId(post.getTopic().getId()); dto.setCreationDate(post.getCreationDate()); dto.setModificationDate(post.getModificationDate()); return dto; }
/** * Populate topicUrl, readIconUrl, unreadIconUrl parameters of dto for topics (Discussion and CodeReview) * * @param dto dto to be populated * * @return populated dto */ private TopicDto populateCoreTopicDto(TopicDto dto) { dto.setTopicUrl(PREFIX + dto.getTopic().getId()); dto.setPostUrlPrefix("/posts/"); if (dto.getTopic().isCodeReview()) { dto.setUnreadIconUrl(CODE_REVIEW_NEW_POSTS); dto.setReadIconUrl(CODE_REVIEW_NO_NEW_POSTS); } else if (dto.getTopic().isClosed()) { dto.setUnreadIconUrl(DISCUSSION_CLOSED_NEW_POSTS); dto.setReadIconUrl(DISCUSSION_CLOSED_NO_NEW_POSTS); } else { dto.setUnreadIconUrl(DISCUSSION_NEW_POSTS); dto.setReadIconUrl(DISCUSSION_NO_NEW_POSTS); } return dto; } }