Compare commits

...

9 Commits

Author SHA1 Message Date
794388d0f6 kaif 2023-06-20 15:49:30 +04:00
e42408413a le maraffette 2023-06-20 14:28:49 +04:00
71dd9b4fde that's all, folks? 2023-06-20 13:51:27 +04:00
263182db1d comments & users start 2023-06-20 13:00:30 +04:00
89c76406ea posts & topics 2023-06-20 12:22:08 +04:00
a038eba691 signup 2023-06-20 10:35:56 +04:00
072a9514b6 services 2023-06-20 10:19:53 +04:00
db18f20ff6 dto + dao 2023-06-20 09:50:11 +04:00
e656fe0664 models 2023-06-20 09:34:21 +04:00
39 changed files with 1873 additions and 3866 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
package com.webproglabs.lab1; package com.webproglabs.lab1;
import com.webproglabs.lab1.models.UserRole;
import com.webproglabs.lab1.mvc.UserSignupMvcController;
import com.webproglabs.lab1.services.UserService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -29,47 +32,47 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String LOGIN_URL = "/login"; private static final String LOGIN_URL = "/login";
public static final String SPA_URL_MASK = "/{path:[^\\.]*}"; public static final String SPA_URL_MASK = "/{path:[^\\.]*}";
// private final ProfileService userService; private final UserService userService;
//
// public SecurityConfiguration(ProfileService userService) {
// this.userService = userService;
// createAdminOnStartup();
// }
// private void createAdminOnStartup() { public SecurityConfiguration(UserService userService) {
// final String admin = "admin"; this.userService = userService;
// if (userService.findByLogin(admin) == null) { createAdminOnStartup();
// log.info("Admin user successfully created"); }
// try {
// userService.createUser(admin, admin, admin, UserRole.ADMIN);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
// }
// @Override private void createAdminOnStartup() {
// protected void configure(HttpSecurity http) throws Exception { final String admin = "admin";
// if (userService.findUserByLogin(admin) == null) {
// http.exceptionHandling().authenticationEntryPoint(delegatingEntryPoint()); log.info("Admin user successfully created");
// http.headers().frameOptions().sameOrigin().and() try {
// .cors().and() userService.createUser(admin, admin, admin, UserRole.ADMIN);
// .csrf().disable() } catch (Exception e) {
// .authorizeRequests() throw new RuntimeException(e);
// .antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll() }
// .antMatchers(HttpMethod.GET, LOGIN_URL).permitAll() }
// .anyRequest().authenticated() }
// .and()
// .formLogin()
// .loginPage(LOGIN_URL).permitAll()
// .and()
// .logout().permitAll();
// }
// @Override @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(HttpSecurity http) throws Exception {
// auth.userDetailsService(userService);
// } http.exceptionHandling().authenticationEntryPoint(delegatingEntryPoint());
http.headers().frameOptions().sameOrigin().and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll()
.antMatchers(HttpMethod.GET, LOGIN_URL).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(LOGIN_URL).permitAll()
.and()
.logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override @Override
public void configure(WebSecurity web) { public void configure(WebSecurity web) {

View File

@ -0,0 +1,12 @@
package com.webproglabs.lab1.dao;
import com.webproglabs.lab1.models.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByTextLike(String text);
Optional<Comment> findById(Long Id);
}

View File

@ -0,0 +1,18 @@
package com.webproglabs.lab1.dao;
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.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByTextLike(String text);
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

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

View File

@ -0,0 +1,12 @@
package com.webproglabs.lab1.dao;
import com.webproglabs.lab1.models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findOneByLoginIgnoreCase(String login);
Optional<User> findById(Long Id);
}

View File

@ -0,0 +1,30 @@
package com.webproglabs.lab1.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.models.Comment;
import javax.validation.constraints.NotBlank;
public class CommentDto {
private Long id;
@NotBlank
private String text;
@NotBlank
private String authorLogin;
public CommentDto(Comment comment) {
this.id = comment.getId();
this.text = comment.getText();
this.authorLogin = comment.getOwner().getLogin();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getText() {return text;}
public void setText(String text) {
this.text = text;
}
public String getAuthor() {return authorLogin;}
}

View File

@ -0,0 +1,41 @@
package com.webproglabs.lab1.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.models.Comment;
import com.webproglabs.lab1.models.Post;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
public class PostDto {
private Long id;
@NotBlank
private String text;
@NotNull
private List<CommentDto> comments = new ArrayList<>();
@NotBlank
private String authorLogin;
public PostDto(Post post){
this.id = post.getId();
this.text = post.getText();
for(Comment comment: post.getComments()){
comments.add(new CommentDto(comment));
}
this.authorLogin = post.getAuthor().getLogin();
}
public PostDto(){}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getText() {return text;}
public void setText(String text) {
this.text = text;
}
public List<CommentDto> getComments() {return comments;}
public String getAuthor() {return authorLogin;}
}

View File

@ -0,0 +1,54 @@
package com.webproglabs.lab1.dto;
import com.webproglabs.lab1.models.Comment;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.Topic;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
public class TopicDto {
private Long id;
@NotBlank
private String name;
@NotBlank
private String description;
@NotNull
private List<PostDto> posts = new ArrayList<>();
public TopicDto(Topic topic) {
this.id = topic.getId();
this.name = topic.getName();
this.description = topic.getDescription();
for(Post post: topic.getPosts()){
posts.add(new PostDto(post));
}
}
public TopicDto(){}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public List<PostDto> getPosts() {
return posts;
}
public void setName(String name) {
this.name = name;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,55 @@
package com.webproglabs.lab1.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.models.Comment;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.User;
import com.webproglabs.lab1.models.UserRole;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
public class UserDto {
private Long id;
@NotBlank
private String login;
@NotBlank
private String password;
@NotNull
private List<CommentDto> comments = new ArrayList<>();
@NotNull
private List<PostDto> posts = new ArrayList<>();
@NotNull
private UserRole role;
public UserDto(){}
public UserDto(User user){
this.id = user.getId();
this.login = user.getLogin();
this.password = user.getPassword();
this.role = user.getRole();
for(Comment comment: user.getComments()){
comments.add(new CommentDto(comment));
}
for (Post post: user.getPosts()) {
posts.add(new PostDto(post));
}
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getLogin() {return login;}
public void setLogin(String login) {this.login = login;}
public String getPassword() {return password;}
public UserRole getRole() {return role;}
public void setPassword(String password) {this.password = password;}
public List<CommentDto> getComments() {return comments;}
public List<PostDto> getPosts() {return posts;}
}

View File

@ -0,0 +1,40 @@
package com.webproglabs.lab1.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class UserSignupDto {
@NotBlank
@Size(min = 4, max = 64)
private String login;
@NotBlank
@Size(min = 4, max = 64)
private String password;
@NotBlank
@Size(min = 4, max = 64)
private String passwordConfirm;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}

View File

@ -0,0 +1,82 @@
package com.webproglabs.lab1.models;
import javax.persistence.*;
import java.util.Objects;
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String text;
@ManyToOne()
private User owner;
@ManyToOne()
private Post post;
public Comment(){};
public Comment(String text, User owner, Post post) {
this.text = text;
this.owner = owner;
this.post = post;
}
public Long getId() {
return id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner.getComments().remove(this);
this.owner = owner;
if (!owner.getComments().contains(this)) {
owner.getComments().add(this);
}
}
public Post getPost() {return post; }
public void setPost(Post post) {
this.post.getComments().remove(this);
this.post = post;
if (!post.getComments().contains(this)) {
post.getComments().add(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Comment comment = (Comment) o;
return Objects.equals(id, comment.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public String ToString() {
return "Comment{" +
"id=" + id +
", text='" + text + '\'' +
", owner='" + owner.ToString() + '\'' +
", post='" + post.ToString() + '\'' +
'}';
}
}

View File

@ -0,0 +1,97 @@
package com.webproglabs.lab1.models;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String text;
@OneToMany(mappedBy = "post", orphanRemoval = true, fetch = FetchType.EAGER)
private List<Comment> comments = new ArrayList<Comment>();
@ManyToOne()
private User author;
@ManyToOne()
private Topic topic;
public Post(){}
public Post(String text, User author, Topic topic) {
this.text = text;
this.author = author;
this.topic = topic;
}
public Long getId() {
return id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author.getPosts().remove(this);
this.author = author;
if (!author.getPosts().contains(this)) {
author.getPosts().add(this);
}
}
public Topic getTopic() {
return topic;
}
public void setTopic(Topic topic) {
this.topic.getPosts().remove(this);
this.topic = topic;
if (!topic.getPosts().contains(this)) {
topic.getPosts().add(this);
}
}
public List<Comment> getComments() { return comments; }
public void addComment(Comment comment) {
this.comments.add(comment);
if (comment.getPost() != this) {
comment.setPost(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Post post = (Post) o;
return Objects.equals(id, post.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public String ToString() {
return "Post{" +
"id=" + id +
", text='" + text + '\'' +
", author='" + author.ToString() + '\'' +
'}';
}
}

View File

@ -0,0 +1,78 @@
package com.webproglabs.lab1.models;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
public class Topic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
@OneToMany(mappedBy = "topic", orphanRemoval = true, fetch = FetchType.EAGER)
private List<Post> posts;
public Topic(String name, String description) {
this.name = name;
this.description = description;
this.posts = new ArrayList<Post>();
}
public Topic(){}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public List<Post> getPosts() {
return posts;
}
public void setName(String name) {
this.name = name;
}
public void setDescription(String description) {
this.description = description;
}
public void addPost(Post post) {
this.posts.add(post);
if (post.getTopic() != this) {
post.setTopic(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Topic topic = (Topic) o;
return Objects.equals(id, topic.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public String ToString() {
return "Topic{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}

View File

@ -0,0 +1,110 @@
package com.webproglabs.lab1.models;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name="users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false, unique = true, length = 64)
@NotBlank
@Size(min = 3, max = 64)
private String login;
@Column(nullable = false, length = 64)
@NotBlank
@Size(min = 4, max = 64)
private String password;
private UserRole role;
@OneToMany(mappedBy = "owner", orphanRemoval = true, fetch = FetchType.EAGER)
private List<Comment> comments = new ArrayList<Comment>();
@OneToMany(mappedBy = "author", orphanRemoval = true, fetch = FetchType.EAGER)
private List<Post> posts = new ArrayList<Post>();
public User(){}
public User(String login, String password) {
this.login = login;
this.password=password;
this.role = UserRole.USER;
};
public User(String login, String password, UserRole role) {
this.login = login;
this.password=password;
this.role = role;
};
public Long getId() {
return id;
}
public String getLogin() {
return login;
}
public UserRole getRole() {
return role;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Comment> getComments() { return comments; }
public List<Post> getPosts() {return posts; }
public void addComment(Comment comment) {
this.comments.add(comment);
if (comment.getOwner() != this) {
comment.setOwner(this);
}
}
public void addPost(Post post) {
this.posts.add(post);
if (post.getAuthor() != this) {
post.setAuthor(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public String ToString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", password='" + password + '\'' +
'}';
}
}

View File

@ -0,0 +1,20 @@
package com.webproglabs.lab1.models;
import org.springframework.security.core.GrantedAuthority;
public enum UserRole implements GrantedAuthority {
ADMIN,
USER;
private static final String PREFIX = "ROLE_";
@Override
public String getAuthority() {
return PREFIX + this.name();
}
public static final class AsString {
public static final String ADMIN = PREFIX + "ADMIN";
public static final String USER = PREFIX + "USER";
}
}

View File

@ -0,0 +1,142 @@
package com.webproglabs.lab1.mvc;
import com.webproglabs.lab1.dto.CommentDto;
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();
}
@GetMapping(value = {"commentModal/{topicId}/{postId}"})
public String getCommentModal(@PathVariable Long topicId,@PathVariable Long postId, Model model) {
model.addAttribute("selectedPost", new PostDto(postService.findPostById(postId)));
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 "commentModal";
}
@PostMapping(value = {"comment/{authorId}/{topicId}/{postId}/"})
public String createComment(@PathVariable Long authorId, @PathVariable Long topicId, @PathVariable Long postId, @RequestParam(value="commentInputField") String commentInputField) {
commentService.addComment(commentInputField, authorId, postId);
return "redirect:/feed/" + topicId.toString();
}
@PostMapping(value = {"/deleteComment/{id}/{topicId}"})
public String deleteComment(@PathVariable Long id, @PathVariable Long topicId) {
commentService.deleteComment(id);
return "redirect:/feed/" + topicId.toString();
}
@GetMapping(value = {"commentEditModal/{id}/{topicId}"})
public String getCommentEditModal(@PathVariable Long id,@PathVariable Long topicId, Model model) {
model.addAttribute("selectedComment", new CommentDto(commentService.findCommentById(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 "editCommentModal";
}
@PostMapping(value = {"editComment/{topicId}/{commentId}/"})
public String editComment(@PathVariable Long topicId,@PathVariable Long commentId, @RequestParam(value="commentEditField") String commentEditField) {
commentService.updateComment(commentId, commentEditField);
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

@ -0,0 +1,79 @@
package com.webproglabs.lab1.mvc;
import com.webproglabs.lab1.dto.UserDto;
import com.webproglabs.lab1.dto.UserSignupDto;
import com.webproglabs.lab1.models.User;
import com.webproglabs.lab1.models.UserRole;
import com.webproglabs.lab1.services.UserService;
import org.springframework.security.access.annotation.Secured;
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.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller
@RequestMapping("/users")
public class UserMvcController {
private final UserService userService;
public UserMvcController(UserService userService) {
this.userService = userService;
}
@GetMapping
@Secured({UserRole.AsString.ADMIN})
public String getUsersPage(Model model){
model.addAttribute("profiles", userService.findAllUsers().stream().map(UserDto::new).toList());
return "users";
}
@PostMapping(value = {"/{id}"})
@Secured({UserRole.AsString.ADMIN})
public String deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return "redirect:/users";
}
@GetMapping(value = {"/{login}"})
public String getUserPage(@PathVariable String login, Model model) {
model.addAttribute("user", new UserDto(userService.findUserByLogin(login)));
return "userPage";
}
@GetMapping(value = {"/settings"})
public String getUserEditPage(Model model) {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var currentUser = userService.findUserByLogin(principal.getUsername());
model.addAttribute("user", new UserDto(userService.findUserById(currentUser.getId())));
model.addAttribute("userDto", new UserSignupDto());
return "userEditPage";
}
@PostMapping(value = {"/edit/{id}"})
public String editUserData(@PathVariable Long id, @ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
BindingResult bindingResult,
Model model) {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var currentUser = userService.findUserByLogin(principal.getUsername());
model.addAttribute("user", new UserDto(userService.findUserById(currentUser.getId())));
model.addAttribute("userDto", new UserSignupDto());
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "userEditPage";
}
try {
final User user = userService.updateUser(id, userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
model.addAttribute("success", "Данные успешно изменены");
return "userEditPage";
} catch (Exception e) {
model.addAttribute("errors", e.getMessage());
return "userEditPage";
}
}
}

View File

@ -0,0 +1,55 @@
package com.webproglabs.lab1.mvc;
import com.webproglabs.lab1.dto.UserSignupDto;
import com.webproglabs.lab1.models.User;
import com.webproglabs.lab1.services.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
@Controller
@RequestMapping()
public class UserSignupMvcController {
public static final String SIGNUP_URL = "/signup";
private final UserService userService;
public UserSignupMvcController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String toFeed() {
return "redirect:/feed";
}
@GetMapping(SIGNUP_URL)
public String showSignupForm(Model model) {
model.addAttribute("userDto", new UserSignupDto());
return "signup";
}
@PostMapping(SIGNUP_URL)
public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "signup";
}
try {
final User user = userService.createUser(userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
return "redirect:/login?created=" + user.getLogin();
} catch (Exception e) {
model.addAttribute("errors", e.getMessage());
return "signup";
}
}
}

View File

@ -0,0 +1,80 @@
package com.webproglabs.lab1.services;
import com.webproglabs.lab1.dao.CommentRepository;
import com.webproglabs.lab1.dao.PostRepository;
import com.webproglabs.lab1.dao.UserRepository;
import com.webproglabs.lab1.models.Comment;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.User;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class CommentService {
private final UserRepository userRepository;
private final CommentRepository commentRepository;
private final PostRepository postRepository;
public CommentService(UserRepository profileRepository, CommentRepository commentRepository, PostRepository postRepository) {
this.userRepository = profileRepository;
this.commentRepository = commentRepository;
this.postRepository = postRepository;
}
@Transactional
public Comment findCommentById(Long id) {
final Optional<Comment> comment = commentRepository.findById(id);
return comment.orElseThrow(EntityNotFoundException::new);
}
@Transactional
public List<Comment> findAllComments() {
return commentRepository.findAll();
}
@Transactional
public Comment addComment(String text, Long userId, Long postId) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Comment data is null or empty");
}
try{
User user = userRepository.findById(userId).get();
Post post = postRepository.findById(postId).get();
Comment comment = new Comment(text, user, post);
user.addComment(comment);
post.addComment(comment);
userRepository.save(user);
postRepository.save(post);
return commentRepository.save(comment);
}
catch (Exception exception) {
return null;
}
}
@Transactional
public Comment updateComment(Long id, String text) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Comment data is null or empty");
}
Comment currentComment = findCommentById(id);
currentComment.setText(text);
final User owner = currentComment.getOwner();
userRepository.save(owner);
final Post post = currentComment.getPost();
postRepository.save(post);
return commentRepository.save(currentComment);
}
@Transactional
public Comment deleteComment(Long id) {
final Comment currentComment = findCommentById(id);
commentRepository.delete(currentComment);
return currentComment;
}
}

View File

@ -0,0 +1,86 @@
package com.webproglabs.lab1.services;
import com.webproglabs.lab1.dao.CommentRepository;
import com.webproglabs.lab1.dao.PostRepository;
import com.webproglabs.lab1.dao.TopicRepository;
import com.webproglabs.lab1.dao.UserRepository;
import com.webproglabs.lab1.models.Comment;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.Topic;
import com.webproglabs.lab1.models.User;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class PostService {
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final UserRepository userRepository;
private final TopicRepository topicRepository;
public PostService(PostRepository postRepository, CommentRepository commentRepository, UserRepository userRepository, TopicRepository topicRepository) {
this.postRepository = postRepository;
this.commentRepository = commentRepository;
this.userRepository = userRepository;
this.topicRepository = topicRepository;
}
@Transactional
public Post findPostById(Long id) {
final Optional<Post> post = postRepository.findById(id);
return post.orElseThrow(EntityNotFoundException::new);
}
@Transactional
public List<Post> findAllPosts() {
return postRepository.findAll();
}
@Transactional
public List<Post> findPostsInTopicByText(String text, Long topicId) {
Topic topic = topicRepository.findById(topicId).get();
return postRepository.findPostsByTextInTopic(text, topic);
}
@Transactional
public Post addPost (String text, Long authorId, Long topicId) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Post data is null or empty");
}
User author = userRepository.findById(authorId).get();
Topic topic = topicRepository.findById(topicId).get();
Post post = new Post(text, author, topic);
author.addPost(post);
topic.addPost(post);
userRepository.save(author);
topicRepository.save(topic);
return postRepository.save(post);
}
@Transactional
public Post updatePost(Long id, String text) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Post data is null or empty");
}
final Post currentPost = findPostById(id);
currentPost.setText(text);
final User author = currentPost.getAuthor();
userRepository.save(author);
final Topic topic = currentPost.getTopic();
topicRepository.save(topic);
return postRepository.save(currentPost);
}
@Transactional
public Post deletePost(Long id) {
final Post currentPost = findPostById(id);
postRepository.delete(currentPost);
return currentPost;
}
}

View File

@ -0,0 +1,62 @@
package com.webproglabs.lab1.services;
import com.webproglabs.lab1.dao.TopicRepository;
import com.webproglabs.lab1.models.Post;
import com.webproglabs.lab1.models.Topic;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.List;
@Service
public class TopicService {
private final TopicRepository topicRepository;
public TopicService(TopicRepository topicRepository) {
this.topicRepository = topicRepository;
}
@Transactional
public Topic addTopic(String name, String description) {
if (!StringUtils.hasText(name) || !StringUtils.hasText(description)) {
throw new IllegalArgumentException("Name is null or empty");
}
final Topic topic = new Topic(name, description);
return topicRepository.save(topic);
}
@Transactional
public Topic findTopicById(Long id) {
final Topic topic = topicRepository.findById(id).orElse(null);
if (topic == null) {
throw new EntityNotFoundException(String.format(" topic with id [%s] is not found", id));
}
return topic;
}
@Transactional
public List<Topic> findAllTopics() {
return topicRepository.findAll();
}
@Transactional
public Topic updateTopic(Long id, String name, String description) {
if (!StringUtils.hasText(name) || !StringUtils.hasText(description)) {
throw new IllegalArgumentException("Name is null or empty");
}
final Topic topic = findTopicById(id);
topic.setName(name);
topic.setDescription(description);
return topicRepository.save(topic);
}
@Transactional
public Topic deleteTopic(Long id) {
final Topic topic = findTopicById(id);
topicRepository.delete(topic);
return topic;
}
}

View File

@ -0,0 +1,102 @@
package com.webproglabs.lab1.services;
import com.webproglabs.lab1.dao.UserRepository;
import com.webproglabs.lab1.models.User;
import com.webproglabs.lab1.models.UserRole;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public User findUserById(Long id) {
final Optional<User> user = userRepository.findById(id);
return user.orElseThrow(EntityNotFoundException::new);
}
@Transactional
public User findUserByLogin(String login) {
final User user = userRepository.findOneByLoginIgnoreCase(login).orElse(null);
return user;
}
@Transactional
public List<User> findAllUsers() {
return userRepository.findAll();
}
@Transactional
public User createUser(String login, String password, String passwordConfirm, UserRole role) {
if (findUserByLogin(login) != null) {
throw new IllegalArgumentException("User " + login + " already exists");
}
final User user = new User(login, passwordEncoder.encode(password), role);
if (!Objects.equals(password, passwordConfirm)) {
throw new IllegalArgumentException("Passwords not equals");
}
return userRepository.save(user);
}
@Transactional
public User createUser(String login, String password, String passwordConfirm) {
return createUser(login, password, passwordConfirm, UserRole.USER);
}
@Transactional
public User updateUser(Long id, String login, String password, String passwordConfirm) {
if (!StringUtils.hasText(login) || !StringUtils.hasText(password)) {
throw new IllegalArgumentException("User data is null or empty");
}
final User currentUser = findUserById(id);
if (Objects.equals(password, currentUser.getPassword())) {
throw new IllegalArgumentException("New password is the same as old");
}
if (!Objects.equals(password, passwordConfirm)) {
throw new IllegalArgumentException("Password mismatch");
}
currentUser.setLogin(login);
currentUser.setPassword(passwordEncoder.encode(password));
return userRepository.save(currentUser);
}
@Transactional
public User deleteUser(Long id) {
final User currentUser = findUserById(id);
userRepository.delete(currentUser);
return currentUser;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final User userEntity = findUserByLogin(username);
if (userEntity == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
}
}

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="commentCreate" tabindex="-1" role="dialog" aria-labelledby="commentCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commentCreateLabel">Создать комментарий</h5>
</div>
<form th:action="@{/feed/comment/{id}/{topicId}/{postId}/{text} (id=${selectedProfile.id}, topicId=${selectedTopic.id}, postId=${selectedPost.id}, text=${commentInputField}) }" method="post" class="modal-body text-center">
<p>Текст комментария:</p>
<input th:value="${commentInputField}" id="commentInputField" name="commentInputField" 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,49 @@
<!DOCTYPE html>
<html lang="ru"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8"/>
<title>СоцСеточка</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="/favicon.svg">
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
<link rel="stylesheet" href="/css/style.css"/>
</head>
<body>
<div class="bg-light w-75 rounded-pill text-center pt-4 pb-4 mt-3 mx-auto">
<p class='text-center h3'> СоцСеточка</p>
</div>
<div>
<p class='h4 mt-2 text-center'>
<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 text-secondary ">Пользователи</a>
<a sec:authorize="isAuthenticated()" href="/feed" class="text-decoration-none m-3 text-secondary ">Лента</a>
<a sec:authorize="isAuthenticated()" href="/users/settings" class="text-decoration-none m-3 text-secondary ">Настройки</a>
<a sec:authorize="isAuthenticated()" href="/logout" class="text-decoration-none m-3 text-secondary ">
Выход
</a>
</p>
</div>
<div >
<div layout:fragment="content"></div>
</div>
</body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
$('#commentCreate').modal('show');
$('#commentEdit').modal('show');
}
</script>
</html>

View File

@ -0,0 +1,30 @@
<!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="commentEdit" tabindex="-1" role="dialog" aria-labelledby="commentEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commentEditLabel">Редактировать комментарий</h5>
</div>
<form th:action="@{/feed/editComment/{id}/{commentId}/{text} (id=${selectedTopic.id}, commentId=${selectedComment.id}, text=${commentEditField}) }" method="post" class="modal-body text-center">
<p>Новый текст комментария:</p>
<input th:attr="value = ${selectedComment.text}" id="commentEditField" name="commentEditField" 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="~{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,13 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<body>
<div class="container" layout:fragment="content">
<div class="alert alert-danger">
<span th:text="${error}"></span>
</div>
<a href="/">На главную</a>
</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 mb-3 w-100" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Выбор темы
</button>
<ul class="dropdown-menu w-100" >
<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,110 @@
<!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='mb-3 mx-auto w-75'>
<div class="text-center w-100 border border-2 bg-light">
<p class="text-center h3" th:text="${selectedTopic.name}"></p>
<p class="text-center fw-light" th:text="${selectedTopic.description}"></p>
</div>
<p class="text-end h3 " 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="@{/users/{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}/{topicId} (id=${comment.id}, topicId=${selectedTopic.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}/{topicId} (id=${comment.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>
</div>
<form th:action="@{/feed/commentModal/{topicId}/{postId}/ ( topicId=${selectedTopic.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

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<body>
<div class="container" layout:fragment="content">
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
Пользователь не найден или пароль указан не верно
</div>
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
Выход успешно произведен
</div>
<div th:if="${param.created}" class="alert alert-success margin-bottom">
Пользователь '<span th:text="${param.created}"></span>' успешно создан
</div>
<form th:action="@{/login}" method="post" class="container-padding">
<div class="mb-3">
<input type="text" name="username" id="username" class="form-control"
placeholder="Логин" required="true" autofocus="true"/>
</div>
<div class="mb-3">
<input type="password" name="password" id="password" class="form-control"
placeholder="Пароль" required="true"/>
</div>
<button type="submit" class="btn btn-success button-fixed">Войти</button>
<a class="btn btn-primary button-fixed" href="/signup">Регистрация</a>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!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">
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
<form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
<div class="mb-3">
<input type="text" class="form-control" th:field="${userDto.login}"
placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
</div>
<div class="mb-3">
<input type="password" class="form-control" th:field="${userDto.password}"
placeholder="Пароль" required="true" minlength="4" maxlength="64"/>
</div>
<div class="mb-3">
<input type="password" class="form-control" th:field="${userDto.passwordConfirm}"
placeholder="Пароль (подтверждение)" required="true" minlength="4" maxlength="64"/>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-success button-fixed">Создать</button>
<a class="btn btn-primary button-fixed" href="/login">Назад</a>
</div>
</form>
</div>
</body>
</html>

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>

View File

@ -0,0 +1,31 @@
<!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 th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
<div th:if="${success}" th:text="${success}" class="margin-bottom text-success"></div>
<div class="text-start mx-auto w-50 border p-5">
<p class='h5'>Изменение данных пользователя</p>
<p th:text="${'Логин: ' + user.login}"></p>
<form action="#" th:action="@{/users/edit/{id} (id=${user.id})}" th:object="${userDto}" method="post">
<p>Новый логин:</p>
<input th:field="${userDto.login}" type="text" class="mb-2 form-control" required="true" autofocus="true" maxlength="64"/>
<p>Новый пароль:</p>
<input th:field="${userDto.password}" type="password" class="mb-2 form-control" required="true" minlength="4" maxlength="64"/>
<p>Повторите пароль:</p>
<input th:field="${userDto.passwordConfirm}" type="password" class="mb-2 form-control" required="true" minlength="4" maxlength="64"/>
<button type="submit" class="btn btn-success" >Сохранить изменения</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!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="text-start mx-auto w-50 border p-5">
<p class='h5'>Профиль</p>
<p th:text="${'Логин: ' + user.login}"></p>
<p class='h6 '>Список постов пользователя: </p>
<div th:if="${user.posts.size()>0}">
<div th:each="post: ${user.posts}">
<p th:text="${post.text}" class="mb-3 ms-2 border p-2"></p>
</div>
</div>
<p th:unless="${user.posts.size()>0}">
Нет постов
</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,36 @@
<!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">
<head>
</head>
<body>
<div layout:fragment="content" class="text-center">
<div th:each="profile: ${profiles}" class="mb-2">
<div class="text-center">
<div class="text-start mx-auto w-50 border shadow p-3">
<p class='h5'>Профиль</p>
<p th:text="${'Логин: ' + profile.login}"></p>
<p class='h6 '>Список постов пользователя: </p>
<div th:if="${profile.posts.size()>0}">
<div th:each="post: ${profile.posts}">
<p th:text="${post.text}" class="mb-3 ms-2 border p-2"></p>
</div>
</div>
<p th:unless="${profile.posts.size()>0}">
Нет постов
</p>
<div class="text-end" th:if="${profile.login!='admin'}">
<form action="#" th:action="@{/users/{id} (id=${profile.id})}" method="post" >
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>