posts & topics

This commit is contained in:
1yuee 2023-06-20 12:22:08 +04:00
parent a038eba691
commit 89c76406ea
14 changed files with 407 additions and 5 deletions

View File

@ -1,7 +1,10 @@
package com.webproglabs.lab1.dao; package com.webproglabs.lab1.dao;
import com.webproglabs.lab1.models.Post; import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.Topic;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -9,4 +12,7 @@ import java.util.Optional;
public interface PostRepository extends JpaRepository<Post, Long> { public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByTextLike(String text); List<Post> findByTextLike(String text);
Optional<Post> findById(Long Id); Optional<Post> findById(Long Id);
@Query("SELECT p FROM Post p WHERE p.topic = :topic AND p.text LIKE %:text%")
List<Post> findPostsByTextInTopic(@Param("text") String text, @Param("topic") Topic topic);
} }

View File

@ -1,10 +1,15 @@
package com.webproglabs.lab1.dao; package com.webproglabs.lab1.dao;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.Topic; import com.webproglabs.lab1.models.Topic;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface TopicRepository extends JpaRepository<Topic, Long> { public interface TopicRepository extends JpaRepository<Topic, Long> {
Optional<Topic> findById(Long Id); Optional<Topic> findById(Long Id);
} }

View File

@ -21,6 +21,7 @@ public class TopicDto {
posts.add(new PostDto(post)); posts.add(new PostDto(post));
} }
} }
public TopicDto(){}
public Long getId() { public Long getId() {
return id; return id;

View File

@ -22,6 +22,7 @@ public class Topic {
this.description = description; this.description = description;
this.posts = new ArrayList<Post>(); this.posts = new ArrayList<Post>();
} }
public Topic(){}
public Long getId() { public Long getId() {
return id; return id;

View File

@ -0,0 +1,97 @@
package com.webproglabs.lab1.mvc;
import com.webproglabs.lab1.dto.PostDto;
import com.webproglabs.lab1.dto.TopicDto;
import com.webproglabs.lab1.dto.UserDto;
import com.webproglabs.lab1.services.CommentService;
import com.webproglabs.lab1.services.PostService;
import com.webproglabs.lab1.services.TopicService;
import com.webproglabs.lab1.services.UserService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/feed")
public class FeedMvcController {
private final UserService userService;
private final PostService postService;
private final CommentService commentService;
private final TopicService topicService;
public FeedMvcController(UserService userService, PostService postService, CommentService commentService, TopicService topicService) {
this.userService = userService;
this.postService = postService;
this.commentService = commentService;
this.topicService = topicService;
}
@GetMapping
public String getFeedPage(Model model) {
model.addAttribute("topics", topicService.findAllTopics().stream().map(TopicDto::new).toList());
return "feed";
}
@GetMapping(value = {"/{id}"})
public String getFeedPageWithTopic(@PathVariable Long id, Model model) {
model.addAttribute("profiles", userService.findAllUsers().stream().map(UserDto::new).toList());
model.addAttribute("posts", topicService.findTopicById(id).getPosts().stream().map(PostDto::new).toList());
model.addAttribute("topics", topicService.findAllTopics().stream().map(TopicDto::new).toList());
UserDetails principal = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var user = userService.findUserByLogin(principal.getUsername());
model.addAttribute("selectedProfile", new UserDto(userService.findUserById(user.getId())));
model.addAttribute("selectedTopic", new TopicDto(topicService.findTopicById(id)));
return "feedPosts";
}
@GetMapping(value= {"/filter/{id}/"})
public String getFeedPageFiltered(@PathVariable Long id, @RequestParam(value="searchField") String searchField, Model model) {
model.addAttribute("profiles", userService.findAllUsers().stream().map(UserDto::new).toList());
model.addAttribute("topics", topicService.findAllTopics().stream().map(TopicDto::new).toList());
UserDetails principal = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var user = userService.findUserByLogin(principal.getUsername());
model.addAttribute("selectedProfile", new UserDto(userService.findUserById(user.getId())));
model.addAttribute("selectedTopic", new TopicDto(topicService.findTopicById(id)));
model.addAttribute("posts", postService.findPostsInTopicByText(searchField, id).stream().map(PostDto::new).toList());
return "feedPosts";
}
@PostMapping(value={"/{topicId}/post/{id}/"})
public String createPost(@PathVariable Long topicId, @PathVariable Long id, @RequestParam(value="postInputField") String postInputField) {
postService.addPost(postInputField, id, topicId);
return "redirect:/feed/" + topicId.toString();
}
@PostMapping(value = {"/deletePost/{id}/{topicId}"})
public String deletePost(@PathVariable Long id, @PathVariable Long topicId) {
postService.deletePost(id);
return "redirect:/feed/" + topicId.toString();
}
@GetMapping(value = {"postModal/{id}/{topicId}"})
public String getPostEditModal(@PathVariable Long id,@PathVariable Long topicId, Model model) {
model.addAttribute("selectedPost", new PostDto(postService.findPostById(id)));
model.addAttribute("profiles", userService.findAllUsers().stream().map(UserDto::new).toList());
model.addAttribute("posts", topicService.findTopicById(topicId).getPosts().stream().map(PostDto::new).toList());
UserDetails principal = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var user = userService.findUserByLogin(principal.getUsername());
model.addAttribute("selectedProfile", new UserDto(userService.findUserById(user.getId())));
model.addAttribute("selectedTopic", new TopicDto(topicService.findTopicById(topicId)));
return "editPostModal";
}
@PostMapping(value = {"editPost/{id}/{topicId}/"})
public String editPost(@PathVariable Long id, @PathVariable Long topicId, @RequestParam(value="postEditField") String postEditField) {
postService.updatePost(id, postEditField);
return "redirect:/feed/" + topicId.toString();
}
}

View File

@ -0,0 +1,58 @@
package com.webproglabs.lab1.mvc;
import com.webproglabs.lab1.dto.TopicDto;
import com.webproglabs.lab1.dto.UserSignupDto;
import com.webproglabs.lab1.models.UserRole;
import com.webproglabs.lab1.services.TopicService;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/topic")
public class TopicMvcController {
private final TopicService topicService;
public TopicMvcController(TopicService topicService) {
this.topicService = topicService;
}
@GetMapping
@Secured({UserRole.AsString.ADMIN})
public String getTopics(Model model) {
model.addAttribute("topics", topicService.findAllTopics().stream().map(TopicDto::new).toList());
model.addAttribute("topicDto", new TopicDto());
return "topics";
}
@PostMapping(value = {"/create"})
@Secured({UserRole.AsString.ADMIN})
public String createTopic(@ModelAttribute TopicDto topicDto) {
topicService.addTopic(topicDto.getName(), topicDto.getDescription());
return "redirect:/topic";
}
@PostMapping(value = {"/delete/{Id}"})
@Secured({UserRole.AsString.ADMIN})
public String deleteTopic(@PathVariable Long Id) {
topicService.deleteTopic(Id);
return "redirect:/topic";
}
@GetMapping(value = {"/edit/{Id}"})
@Secured({UserRole.AsString.ADMIN})
public String getTopicEdit(@PathVariable Long Id, Model model) {
model.addAttribute("topic", new TopicDto(topicService.findTopicById(Id)));
model.addAttribute("topicDto", new TopicDto(topicService.findTopicById(Id)));
return "topicEdit";
}
@PostMapping(value = {"/edit/{Id}"})
@Secured({UserRole.AsString.ADMIN})
public String editTopic(@PathVariable Long Id, @ModelAttribute TopicDto topicDto) {
topicService.updateTopic(Id, topicDto.getName(), topicDto.getDescription());
return "redirect:/topic";
}
}

View File

@ -57,6 +57,12 @@ public class PostService {
return postList; return postList;
} }
@Transactional
public List<Post> findPostsInTopicByText(String text, Long topicId) {
Topic topic = topicRepository.findById(topicId).get();
return postRepository.findPostsByTextInTopic(text, topic);
}
@Transactional @Transactional
public Post addPost (String text, Long authorId, Long topicId) { public Post addPost (String text, Long authorId, Long topicId) {
if (!StringUtils.hasText(text)) { if (!StringUtils.hasText(text)) {

View File

@ -24,10 +24,10 @@
</div> </div>
<div> <div>
<p class='h4 text-center'> <p class='h4 text-center'>
<a sec:authorize="hasRole('ROLE_ADMIN')" href="/topic" class="text-decoration-none m-3">Топики</a> <a sec:authorize="hasRole('ROLE_ADMIN')" href="/topic" class="text-decoration-none m-3 text-secondary">Топики</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" href="/users" class="text-decoration-none m-3">Пользователи</a> <a sec:authorize="hasRole('ROLE_ADMIN')" href="/users" class="text-decoration-none m-3 text-secondary">Пользователи</a>
<a sec:authorize="isAuthenticated()" href="/feed" class="text-decoration-none m-3">Лента</a> <a sec:authorize="isAuthenticated()" href="/feed" class="text-decoration-none m-3 text-secondary">Лента</a>
<a sec:authorize="isAuthenticated()" href="/logout" class="text-decoration-none m-3"> <a sec:authorize="isAuthenticated()" href="/logout" class="text-decoration-none m-3 text-secondary">
Выход Выход
</a> </a>
</p> </p>
@ -37,5 +37,12 @@
<div layout:fragment="content"></div> <div layout:fragment="content"></div>
</div> </div>
</body> </body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
$('#commentCreate').modal('show');
$('#commentEdit').modal('show');
}
</script>
</html> </html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{feedPosts}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="modalFeed">
<div class="modal fade" id="postEdit" tabindex="-1" role="dialog" aria-labelledby="postEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postEditLabel">Редактировать пост</h5>
</div>
<form th:action="@{/feed/editPost/{id}/{topicId}/{text} (id=${selectedPost.id}, topicId=${selectedTopic.id}, text=${postEditField} ) }" method="post" class="modal-body text-center">
<p>Текст поста:</p>
<input th:attr="value = ${selectedPost.text}" id="postEditField" name="postEditField" type="text" class="mb-2">
<br>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary" >Сохранить</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="text-center">
<div class="dropdown text-center mx-auto w-75">
<div class="text-center mt-3">
<button class="btn btn-secondary dropdown-toggle ms-3 mb-3 w-50" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Выбор топика
</button>
<ul class="dropdown-menu w-50" >
<li th:each="topic: ${topics}">
<a class="dropdown-item" th:href="@{/feed/{id}(id=${topic.id})}" th:text="${topic.name + '. ' + topic.description}">
</a>
</li>
</ul>
</div>
</div>
<div layout:fragment="contentFeed"></div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{feed}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="contentFeed">
<div class='h3 mb-3 mx-auto w-75'>
<p class="text-center h3" th:text="${selectedTopic.name}"></p>
<p class="text-end" th:text="${selectedProfile.login}"></p>
</div>
<div class='h3 m-3 d-flex justify-content-between text-center mx-auto w-75'>
Лента
<button type='button' class="btn btn-primary ms-5 mb-3 " data-bs-toggle="modal" data-bs-target="#postCreate">
Добавить новый пост
</button>
</div>
<form th:action="@{/feed/filter/{id}/{text} (id=${selectedTopic.id}, text=${searchField}) }" method="get">
<input th:value="${searchField}" id="searchField" name="searchField" type="text" class="mb-2" style="width: 70%" placeholder="Поиск...">
<button type='submit' class="btn btn-primary mb-2" style="width: 5%">
Найти
</button>
</form>
<div th:each="post: ${posts}" class="text-center mx-auto w-75 mb-3 ">
<div class="border p-2">
<p th:text="${post.text}" class="h4 text-start"></p>
<div class="d-flex justify-content-between fst-italic">
<div>
Автор:
<a th:text="${post.getAuthor()}" class="text-start fst-italic" th:href="@{/profile/{login}(login=${post.getAuthor()})}"></a>
</div>
<div th:if="${selectedProfile.getLogin() == post.getAuthor()}" class="d-flex justify-content-between fst-italic">
<form th:action="@{/feed/deletePost/{id}/{topicId} (id=${post.id}, topicId=${selectedTopic.id})}" method="post" >
<button type="submit" class="btn btn-danger me-1 mb-1 ms-2" >
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</form>
<form th:action="@{/feed/postModal/{id}/{topicId} (id=${post.id}, topicId=${selectedTopic.id})}" method="get">
<button type="submit" class="btn btn-warning mb-1" data-bs-toggle="modal" data-bs-target="#postEdit" >
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</form>
</div>
</div>
</div>
<div class="border p-2" th:if="${post.comments.size() > 0}" >
<div class="text-start">
<p class="text-start h5">Комментарии:</p>
<div th:each="comment: ${post.comments}" >
<p class="fst-italic" th:text="${comment.getAuthor() + ':'}"> </p>
<div class="d-flex justify-content-between fst-italic">
<p class="ms-3" th:text="${comment.text}"></p>
<div th:if="${selectedProfile.getLogin() == comment.getAuthor()}" class="d-flex justify-content-between fst-italic">
<form th:action="@{/feed/deleteComment/{id}/{authorId} (id=${comment.id}, authorId=${selectedProfile.id})}" method="post" >
<button type="submit" class="btn btn-danger me-1 mb-1"> <i class="fa fa-trash" aria-hidden="true"> </i> </button>
</form>
<form th:action="@{/feed/commentEditModal/{id}/{authorId} (id=${comment.id}, authorId=${selectedProfile.id})}" method="get">
<button type="submit" class="btn btn-warning mb-1" data-bs-toggle="modal" data-bs-target="#postEdit" >
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<form th:action="@{/feed/commentModal/{authorId}/{postId}/ ( authorId=${selectedProfile.id}, postId=${post.id} ) }" method="get" class="text-end">
<button type="submit" class="btn btn-info mb-3" data-bs-toggle="modal" data-bs-target="#commentCreate">Добавить комментарий</button>
</form>
</div>
<div class="modal fade" id="postCreate" tabindex="-1" role="dialog" aria-labelledby="postCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postCreateLabel">Создать пост</h5>
</div>
<form th:action="@{/feed/{topicId}/post/{id}/{text} (topicId=${selectedTopic.id}, id=${selectedProfile.id}, text=${postInputField}) }" method="post" class="modal-body text-center">
<p>Текст поста:</p>
<input th:value="${postInputField}" id="postInputField" name="postInputField" type="text" class="mb-2">
<br>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary" >Сохранить</button>
</form>
</div>
</div>
</div>
<div layout:fragment="modalFeed"></div>
</div>
</body>
</html>

View File

@ -1,4 +1,3 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" <html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}"
xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div class="container container-padding" layout:fragment="content">
<div class="h4 text-center">Изменить топик</div>
<form action="#" th:action="@{/topic/edit/{id} (id=${topic.id})}" th:object="${topicDto}" method="post">
<p>Новое название:</p>
<input th:field="${topicDto.name}" type="text" class="mb-2 form-control" required="true" />
<p>Новое описание:</p>
<input th:field="${topicDto.description}" type="text" class="mb-2 form-control" required="true" />
<button type="submit" class="btn btn-success" >Сохранить изменения</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<body>
<div class="container container-padding" layout:fragment="content">
<form action="#" th:action="@{/topic/create}" th:object="${topicDto}" method="post">
<p>Название:</p>
<input th:field="${topicDto.name}" type="text" class="mb-2 form-control" required="true" />
<p>Описание:</p>
<input th:field="${topicDto.description}" type="text" class="mb-2 form-control" required="true" />
<button type="submit" class="btn btn-success">Добавить</button>
</form>
<div th:each="topic: ${topics}" class="border p-3 m-3 shadow-lg bg-body rounded">
<div th:text="${'Топик: ' + topic.name}"></div>
<div th:text="${'Описание: ' + topic.description}"></div>
<form action="#" th:action="@{/topic/edit/{id} (id=${topic.id})}" method="get" class="text-end m-1">
<button type="submit" class="btn btn-warning">Изменить</button>
</form>
<form action="#" th:action="@{/topic/delete/{id} (id=${topic.id})}" method="post" class="text-end m-1">
<button type="submit" class="btn btn-danger" >Удалить</button>
</form>
</div>
</div>
</body>
</html>