diff --git a/src/main/java/site/easystartup/web/domain/User.java b/src/main/java/site/easystartup/web/domain/User.java index 182f51c..51253cd 100644 --- a/src/main/java/site/easystartup/web/domain/User.java +++ b/src/main/java/site/easystartup/web/domain/User.java @@ -60,4 +60,19 @@ public class User { public int hashCode() { return getClass().hashCode(); } + + public boolean isModer() { + if (roles.contains(Role.MODER) || roles.contains(Role.ADMIN)) return true; + return false; + } + + public boolean isAdmin() { + if (roles.contains(Role.ADMIN)) return true; + return false; + } + + public boolean isPremium() { + if (roles.contains(Role.PREMIUM) || roles.contains(Role.ADMIN)) return true; + return false; + } } diff --git a/src/main/java/site/easystartup/web/forum/controller/ForumController.java b/src/main/java/site/easystartup/web/forum/controller/ForumController.java index e9f9eb1..6b865aa 100644 --- a/src/main/java/site/easystartup/web/forum/controller/ForumController.java +++ b/src/main/java/site/easystartup/web/forum/controller/ForumController.java @@ -3,17 +3,19 @@ package site.easystartup.web.forum.controller; import lombok.RequiredArgsConstructor; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.annotation.Secured; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; +import site.easystartup.web.domain.User; +import site.easystartup.web.forum.domain.Discussion; +import site.easystartup.web.forum.domain.DiscussionMessage; +import site.easystartup.web.forum.domain.DiscussionStatus; import site.easystartup.web.forum.domain.Topic; -import site.easystartup.web.forum.filter.ForumFilterService; +import site.easystartup.web.forum.service.ForumService; import site.easystartup.web.forum.repo.TopicRepo; +import site.easystartup.web.service.UserService; import java.util.Date; @@ -22,7 +24,9 @@ import java.util.Date; public class ForumController { private final TopicRepo topicRepo; - private final ForumFilterService forumFilterService; + private final ForumService forumService; + + private final UserService userService; @GetMapping("/forum") public String forumMain(Model model) { @@ -33,8 +37,8 @@ public class ForumController { @GetMapping("/forum/my") public String forumMy(Model model) { - User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - val discussions = forumFilterService.getDiscussionsByAuthor(user.getUsername()); + User user = userService.getCurrentUser(); + val discussions = forumService.getDiscussionsByAuthor(user.getUsername()); model.addAttribute("discussions", discussions); return "/my-discussions"; } @@ -44,37 +48,120 @@ public class ForumController { return "topic-new"; } + @Secured({"ADMIN", "MODER"}) @PostMapping("/forum/new") public String forumNewTopic(@RequestParam String title, @RequestParam String description) { - description = description == null ? "" : description; - User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - Topic topic = new Topic(); - topic.setTitle(title); - topic.setDescription(description); - topic.setAuthor(user.getUsername()); - topic.setLast_update(new Date()); - topic.setSize(0); - topicRepo.save(topic); + forumService.createTopic(title, description, userService.getCurrentUser()); return "/forum"; + } @GetMapping("/forum/{topic_id}") public String topicFirstPage(@PathVariable(value = "topic_id") long topic_id, Model model) { - val pinned_discussions = forumFilterService.getPinnedDiscussionsByTopicId(topic_id); + val pinned_discussions = forumService.getPinnedDiscussionsByTopicId(topic_id); model.addAttribute("pinned_discussions", pinned_discussions); - val discussions = forumFilterService.getSortedDiscussionsByTopicId(topic_id).subList(0, 14); + val discussions = forumService.getSortedDiscussionsByTopicId(topic_id).subList(0, 14); model.addAttribute("discussions", discussions); - return "topic-first"; + return "/topic-first"; } @GetMapping("/forum/{topic_id}/page/{page_index}") public String topicPage(@PathVariable(value = "topic_id") long topic_id, - @PathVariable(value = "topic_id") long page_index, - Model model) { - val discussions = forumFilterService.getSortedDiscussionsByTopicId(topic_id).subList((int)(15 * page_index), (int)(15 * page_index) + 15); + @PathVariable(value = "page_index") long page_index, + Model model) { + val discussions = forumService.getSortedDiscussionsByTopicId(topic_id).subList((int)(15 * page_index), (int)(15 * page_index) + 15); model.addAttribute("discussions", discussions); - return "topic-content"; + return "/topic-content"; } + + @GetMapping("/forum/discussion/{discussion_id}") + public String discussion(@PathVariable(value = "discussion_id") long discussion_id, + Model model) { + model.addAttribute("discussion", forumService.getDiscussionById(discussion_id)); + model.addAttribute("messages ", forumService.getMessagesByDiscussionId(discussion_id)); + return "/discussion"; + } + + @GetMapping("/forum/{topic_id}/create") + public String discussionCreate(@PathVariable long topic_id) { + return "/discussion-create"; + } + + @PostMapping("/forum/{topic_id}/create") + public String discussionCreatePost(@PathVariable long topic_id, + @RequestParam String title) { + forumService.createDiscussion(title, topic_id, userService.getCurrentUser()); + return "/discussion-create"; + } + + @GetMapping("/forum/discussion/{discussion_id}/edit") + public String discussionEdit(@PathVariable long discussion_id, Model model) { + Discussion discussion = forumService.getDiscussionById(discussion_id); + User user = userService.getCurrentUser(); + if(!(discussion.getAuthor().equals(user.getUsername()) || user.isModer())) return "/403-error"; + model.addAttribute("discussion", discussion); + return "/discussion-edit"; + } + + @PostMapping("/forum/discussion/{discussion_id}/edit") + public String discussionEditPost(@PathVariable long discussion_id, + @RequestParam String title, + @RequestParam DiscussionStatus status, + Model model) { + forumService.editDiscussion(title, discussion_id, status, userService.getCurrentUser()); + model.addAttribute("discussion", forumService.getDiscussionById(discussion_id)); + model.addAttribute("messages ", forumService.getMessagesByDiscussionId(discussion_id)); + return "/discussion"; + } + + @GetMapping("/forum/discussion/{discussion_id}/add") + public String messageAdd(@PathVariable long discussion_id) { + return "/discussion-message-add"; + } + + @PostMapping("/forum/discussion/{discussion_id}/add") + public String messageAddPost(@PathVariable long discussion_id, + @RequestParam String text, + Model model) { + forumService.addDiscussionMessage(text, discussion_id, userService.getCurrentUser()); + model.addAttribute("discussion", forumService.getDiscussionById(discussion_id)); + model.addAttribute("messages ", forumService.getMessagesByDiscussionId(discussion_id)); + return "/discussion"; + } + + @GetMapping("/forum/discussion/{discussion_id}/{message_id}/edit") + public String messageEdit(@PathVariable long discussion_id, + @PathVariable long message_id) { + DiscussionMessage message = forumService.getDiscussionMessageById(message_id); + User user = userService.getCurrentUser(); + if(!(message.getSender().equals(user.getUsername()) || user.isModer())) return "/403-error"; + return "/discussion-message-edit"; + } + + @PostMapping("/forum/discussion/{discussion_id}/{message_id}/edit") + public String messageEditPost(@PathVariable long discussion_id, + @RequestParam String text, + Model model, @PathVariable String message_id) { + forumService.editDiscussionMessage(text, discussion_id, userService.getCurrentUser()); + model.addAttribute("discussion", forumService.getDiscussionById(discussion_id)); + model.addAttribute("messages ", forumService.getMessagesByDiscussionId(discussion_id)); + return "/discussion"; + } + + @DeleteMapping("/forum/discussion/{discussion_id}/{message_id}/delete") + public String messageDelete(@PathVariable long discussion_id, + @PathVariable long message_id, + Model model) { + DiscussionMessage message = forumService.getDiscussionMessageById(message_id); + User user = userService.getCurrentUser(); + if(!(message.getSender().equals(user.getUsername()) || user.isModer())) return "/403-error"; + + model.addAttribute("discussion", forumService.getDiscussionById(discussion_id)); + model.addAttribute("messages ", forumService.getMessagesByDiscussionId(discussion_id)); + return "/discussion"; + } + + } diff --git a/src/main/java/site/easystartup/web/forum/domain/DiscussionMessage.java b/src/main/java/site/easystartup/web/forum/domain/DiscussionMessage.java index bac887f..13ca691 100644 --- a/src/main/java/site/easystartup/web/forum/domain/DiscussionMessage.java +++ b/src/main/java/site/easystartup/web/forum/domain/DiscussionMessage.java @@ -21,6 +21,7 @@ public class DiscussionMessage { private String sender; private Date date; private boolean edited; + private long discussion_id; @Override public boolean equals(Object o) { diff --git a/src/main/java/site/easystartup/web/forum/filter/ForumFilterService.java b/src/main/java/site/easystartup/web/forum/filter/ForumFilterService.java deleted file mode 100644 index eda3a7c..0000000 --- a/src/main/java/site/easystartup/web/forum/filter/ForumFilterService.java +++ /dev/null @@ -1,51 +0,0 @@ -package site.easystartup.web.forum.filter; - -import com.google.common.collect.Lists; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import site.easystartup.web.forum.domain.Discussion; -import site.easystartup.web.forum.domain.DiscussionMessage; -import site.easystartup.web.forum.repo.DiscussionMessageRepo; -import site.easystartup.web.forum.repo.DiscussionRepo; - -import java.util.Collections; -import java.util.List; - -@Component -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class ForumFilterService { - private final DiscussionMessageRepo discussionMessageRepo; - - private final DiscussionRepo discussionRepo; - - public List getMessagesByDiscussionId(long discussionId) { - return Lists.newArrayList(discussionMessageRepo.findAll()).stream() - .filter(m -> m.getId() == discussionId).toList(); - } - - public List getDiscussionsByAuthor(String author) { - return Lists.newArrayList(discussionRepo.findAll()).stream() - .filter(d -> d.getAuthor().equals(author)).toList(); - } - - public List getDiscussionsByMember(String member) { - return Lists.newArrayList(discussionRepo.findAll()).stream() - .filter(d -> getMessagesByDiscussionId(d.getId()).stream() - .anyMatch(m -> m.getSender().equals(member))).toList(); - } - - public List getPinnedDiscussionsByTopicId(long topic_id) { - return Lists.newArrayList(discussionRepo.findAll()).stream() - .filter(Discussion::isPinned) - .filter(d -> d.getTopic() == topic_id).toList(); - } - - public List getSortedDiscussionsByTopicId(long topic_id) { - List result = Lists.newArrayList(discussionRepo.findAll()).stream() - .filter(d -> !d.isPinned()) - .filter(d -> d.getTopic() == topic_id).toList(); - Collections.sort(result, Discussion::compareTo); - return result; - } -} diff --git a/src/main/java/site/easystartup/web/forum/service/ForumService.java b/src/main/java/site/easystartup/web/forum/service/ForumService.java new file mode 100644 index 0000000..b2c0f23 --- /dev/null +++ b/src/main/java/site/easystartup/web/forum/service/ForumService.java @@ -0,0 +1,131 @@ +package site.easystartup.web.forum.service; + +import com.google.common.collect.Lists; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import site.easystartup.web.domain.Role; +import site.easystartup.web.domain.User; +import site.easystartup.web.forum.domain.Discussion; +import site.easystartup.web.forum.domain.DiscussionMessage; +import site.easystartup.web.forum.domain.DiscussionStatus; +import site.easystartup.web.forum.domain.Topic; +import site.easystartup.web.forum.repo.DiscussionMessageRepo; +import site.easystartup.web.forum.repo.DiscussionRepo; +import site.easystartup.web.forum.repo.TopicRepo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ForumService { + private final TopicRepo topicRepo; + + private final DiscussionMessageRepo discussionMessageRepo; + + private final DiscussionRepo discussionRepo; + + public List getMessagesByDiscussionId(long discussionId) { + return Lists.newArrayList(discussionMessageRepo.findAll()).stream() + .filter(m -> m.getDiscussion_id() == discussionId).toList(); + } + + public Discussion getDiscussionById(long discussionId) { + return discussionRepo.findById(discussionId).get(); + } + + public List getDiscussionsByAuthor(String author) { + return Lists.newArrayList(discussionRepo.findAll()).stream() + .filter(d -> d.getAuthor().equals(author)).toList(); + } + + public List getDiscussionsByMember(String member) { + return Lists.newArrayList(discussionRepo.findAll()).stream() + .filter(d -> getMessagesByDiscussionId(d.getId()).stream() + .anyMatch(m -> m.getSender().equals(member))).toList(); + } + + public List getPinnedDiscussionsByTopicId(long topic_id) { + return Lists.newArrayList(discussionRepo.findAll()).stream() + .filter(Discussion::isPinned) + .filter(d -> d.getTopic() == topic_id).toList(); + } + + public List getSortedDiscussionsByTopicId(long topic_id) { + List result = Lists.newArrayList(discussionRepo.findAll()).stream() + .filter(d -> !d.isPinned()) + .filter(d -> d.getTopic() == topic_id).toList(); + Collections.sort(result, Discussion::compareTo); + return result; + } + + public void createTopic(String title, String description, User user) { + description = description == null ? "" : description; + Topic topic = new Topic(); + topic.setTitle(title); + topic.setDescription(description); + topic.setAuthor(user.getUsername()); + topic.setLast_update(new Date()); + topic.setSize(0); + topicRepo.save(topic); + } + + public void createDiscussion(String title, long topic, User user) { + Discussion discussion = new Discussion(); + discussion.setAuthor(user.getUsername()); + discussion.setDate(new Date()); + discussion.setTitle(title); + discussion.setTopic(topic); + discussion.setMessages(new ArrayList<>()); + discussion.setLast_update(new Date()); + discussion.setStatus(DiscussionStatus.OPEN); + discussion.setPinned(false); + discussionRepo.save(discussion); + } + + public void editDiscussion(String title, long discussion_id, DiscussionStatus status, User user) { + Discussion discussion = discussionRepo.findById(discussion_id).orElseThrow(); + if(!(discussion.getAuthor().equals(user.getUsername()) || user.isModer())) throw new SecurityException(); + discussion.setAuthor(user.getUsername()); + discussion.setTitle(title); + discussion.setLast_update(new Date()); + discussion.setStatus(status); + discussionRepo.save(discussion); + } + + public void addDiscussionMessage(String text, long discussion_id, User user) { + DiscussionMessage message = new DiscussionMessage(); + message.setDate(new Date()); + message.setEdited(false); + message.setSender(user.getUsername()); + message.setText(text); + message.setDiscussion_id(discussion_id); + discussionMessageRepo.save(message); + Discussion discussion = discussionRepo.findById(discussion_id).orElseThrow(); + List newMsgs = discussion.getMessages(); + newMsgs.add(message.getId()); + discussion.setMessages(newMsgs); + discussionRepo.save(discussion); + } + + public void editDiscussionMessage(String text, long message_id, User user) { + DiscussionMessage message = discussionMessageRepo.findById(message_id).orElseThrow(); + if(!(message.getSender().equals(user.getUsername()) || user.isAdmin())) throw new SecurityException(); + message.setEdited(true); + message.setText(text); + discussionMessageRepo.save(message); + } + + public void deleteDiscussionMessage(long message_id, User user) { + DiscussionMessage message = discussionMessageRepo.findById(message_id).orElseThrow(); + if(!(message.getSender().equals(user.getUsername()) || user.isAdmin())) throw new SecurityException(); + discussionMessageRepo.delete(message); + } + + public DiscussionMessage getDiscussionMessageById(long id) { + return discussionMessageRepo.findById(id).orElseThrow(); + } +} diff --git a/src/main/java/site/easystartup/web/service/UserNotAuthenticatedException.java b/src/main/java/site/easystartup/web/service/UserNotAuthenticatedException.java new file mode 100644 index 0000000..4053c77 --- /dev/null +++ b/src/main/java/site/easystartup/web/service/UserNotAuthenticatedException.java @@ -0,0 +1,7 @@ +package site.easystartup.web.service; + +public class UserNotAuthenticatedException extends RuntimeException { + public UserNotAuthenticatedException() { + super("The user is not logged in to the system!"); + } +} diff --git a/src/main/java/site/easystartup/web/service/UserService.java b/src/main/java/site/easystartup/web/service/UserService.java index 92513c6..7d56357 100644 --- a/src/main/java/site/easystartup/web/service/UserService.java +++ b/src/main/java/site/easystartup/web/service/UserService.java @@ -1,7 +1,8 @@ package site.easystartup.web.service; import lombok.RequiredArgsConstructor; -import org.hibernate.query.criteria.internal.predicate.PredicateImplementor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import site.easystartup.web.domain.User; @@ -21,4 +22,12 @@ public class UserService { public User getUserById(Long userId) { return userRepo.findById(userId).orElseThrow(() -> new UsernameNotFoundException("User not found")); } + + public User getCurrentUser() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if(auth.isAuthenticated()) throw new UserNotAuthenticatedException(); + org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) + auth.getPrincipal(); + return userRepo.findByUsername(user.getUsername()); + } } diff --git a/src/main/resources/templates/403-error.html b/src/main/resources/templates/403-error.html new file mode 100644 index 0000000..55de862 --- /dev/null +++ b/src/main/resources/templates/403-error.html @@ -0,0 +1,10 @@ + + + + + 403 + + +

You have hot permissions for this!

+ + \ No newline at end of file diff --git a/src/main/resources/templates/discussion-create.html b/src/main/resources/templates/discussion-create.html new file mode 100644 index 0000000..af99d90 --- /dev/null +++ b/src/main/resources/templates/discussion-create.html @@ -0,0 +1,9 @@ + + + + + 403 + + + + \ No newline at end of file diff --git a/src/main/resources/templates/discussion-edit.html b/src/main/resources/templates/discussion-edit.html new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/templates/discussion-message-add.html b/src/main/resources/templates/discussion-message-add.html new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/templates/discussion-message-edit.html b/src/main/resources/templates/discussion-message-edit.html new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/templates/discussion.html b/src/main/resources/templates/discussion.html new file mode 100644 index 0000000..af99d90 --- /dev/null +++ b/src/main/resources/templates/discussion.html @@ -0,0 +1,9 @@ + + + + + 403 + + + + \ No newline at end of file