improved registration (more stronger validation now). changed comments list for admins: they can see deleted comments entries and are allowed to undelete them

master
Vyacheslav N. Boyko 2017-12-12 15:59:37 +03:00
parent 2405116185
commit 188fe4fbcb
17 changed files with 161 additions and 74 deletions

View File

@ -65,9 +65,14 @@ public class PostController {
}
@RequestMapping(value = "{permalink}", method = GET)
public String show(@PathVariable String permalink, Model model, @RequestParam(defaultValue = "0") int page, HttpServletRequest request){
public String show(@PathVariable String permalink, Model model, @RequestParam(defaultValue = "0") int page, HttpServletRequest request) {
Post post = this.postService.findPostByPermalink(permalink);
User user = userService.currentUser();
Boolean isAdmin = userService.hasPrivilege(user, PrivilegeService.PRIVILEGE_ADMIN);
if (post.getCensored() && !userService.hasPrivilege(user, PrivilegeService.PRIVILEGE_WRITE)) {
throw new AccessDeniedException("You are not allowed here");
}
logger.debug(String.format("ACCESS %s from IP: %s", permalink, this.requestProcessorService.getRealIp(request)));
@ -93,13 +98,26 @@ public class PostController {
commentForm.setPostId(post.getId());
Integer commentsPageSize = appSetting.getCommentsPageSize();
Integer lastPage = commentService.getLastPageCommentsForPost(post, commentsPageSize);
Integer lastPage = null;
if (isAdmin) {
lastPage = commentService.getLastPageCommentsForPostForAdmin(post, commentsPageSize);
} else {
lastPage = commentService.getLastPageCommentsForPost(post, commentsPageSize);
}
Page<Comment> comments = null;
if (page > 0) {
comments = commentService.getCommentsForPost(post, page - 1, commentsPageSize);
if (isAdmin) {
comments = commentService.getCommentsForPostForAdmin(post, page - 1, commentsPageSize);
} else {
comments = commentService.getCommentsForPost(post, page - 1, commentsPageSize);
}
} else {
page = lastPage+1;
comments = commentService.getCommentsForPost(post, lastPage, commentsPageSize);
if (isAdmin) {
comments = commentService.getCommentsForPostForAdmin(post, lastPage, commentsPageSize);
} else {
comments = commentService.getCommentsForPost(post, lastPage, commentsPageSize);
}
}
model.addAttribute("page", page);
@ -108,7 +126,7 @@ public class PostController {
model.addAttribute("comments", comments);
model.addAttribute("commentForm", commentForm);
model.addAttribute("commentFormats", commentService.getAvailableCommentFormats());
model.addAttribute("disableCommenting", userService.hasPrivilege(user, PrivilegeService.PRIVILEGE_OWNER) || post.getUser().getId().equals(user.getId()) ? false : post.getDisableCommenting());
model.addAttribute("disableCommenting", userService.currentUserCanWriteCommentToPost(post));
return "posts/show";
}
@ -118,14 +136,16 @@ public class PostController {
public String addComment(@PathVariable String permalink, @Valid CommentForm commentForm, Errors errors, Model model) {
User user = userService.currentUser();
if (!userService.currentUserHasPrivilege(PrivilegeService.PRIVILEGE_WRITE)) {
Post post = postService.getPost(commentForm.getPostId());
if (!userService.currentUserCanWrite() || !userService.currentUserCanWriteCommentToPost(post)) {
throw new AccessDeniedException("You are not allowed here");
}
Comment comment = new Comment();
DTOUtil.mapTo(commentForm, comment);
comment.setUser(user);
comment.setPost(postService.getPost(commentForm.getPostId()));
comment.setPost(post);
comment.setParentComment(commentService.getCommentById(commentForm.getParentCommentId()));
comment.setDepth(commentService.calculateDepth(comment));

View File

@ -23,6 +23,8 @@ import ru.bvn13.voidforum.error.NicknameExistsException;
import ru.bvn13.voidforum.forms.RegistrationForm;
import ru.bvn13.voidforum.models.User;
import ru.bvn13.voidforum.services.UserService;
import ru.bvn13.voidforum.support.web.Message;
import ru.bvn13.voidforum.support.web.MessageHelper;
import ru.bvn13.voidforum.utils.DTOUtil;
import javax.servlet.http.HttpServletRequest;
@ -47,15 +49,26 @@ public class UserController {
@GetMapping(value = "register")
public String registrationForm(Model model) {
model.addAttribute("form", new RegistrationForm());
if (!model.containsAttribute("registrationForm")) {
// it contains it after post request when errors occurred and was redirected
model.addAttribute("registrationForm", new RegistrationForm());
}
return "users/register";
}
@PostMapping(value = "register")
public String register(@Valid RegistrationForm registrationForm, Errors errors, Model model, RedirectAttributes ra) {
if (errors.hasErrors()) {
MessageHelper.addNamedErrorsAsList(ra, "errors", "Please check errors:", errors);
ra.addFlashAttribute("registrationForm", registrationForm);
return "redirect:/register";
}
if (!registrationForm.getPassword().equals(registrationForm.getPasswordCheck())) {
ra.addFlashAttribute("error", "Verify your password!");
MessageHelper.addNamedErrorAttribute(ra, "error", "Verify your password!");
ra.addFlashAttribute("registrationForm", registrationForm);
return "redirect:/register";
}
@ -63,7 +76,8 @@ public class UserController {
|| registrationForm.getNickname().isEmpty()
|| registrationForm.getPassword().isEmpty()
|| registrationForm.getPasswordCheck().isEmpty()) {
ra.addFlashAttribute("error", "Not all necessary fields are specified");
MessageHelper.addNamedErrorAttribute(ra, "error", "Not all necessary fields are specified");
ra.addFlashAttribute("registrationForm", registrationForm);
return "redirect:/register";
}
@ -76,12 +90,12 @@ public class UserController {
try {
userService.registerNewUserAccount(user);
} catch (EmailExistsException e) {
e.printStackTrace();
ra.addFlashAttribute("error", "There is an account with specified email and nickname");
MessageHelper.addNamedErrorAttribute(ra, "error", e.getMessage());
ra.addFlashAttribute("registrationForm", registrationForm);
return "redirect:/register";
} catch (NicknameExistsException e) {
e.printStackTrace();
ra.addFlashAttribute("error", "There is an account with specified email and nickname");
MessageHelper.addNamedErrorAttribute(ra, "error", e.getMessage());
ra.addFlashAttribute("registrationForm", registrationForm);
return "redirect:/register";
}

View File

@ -1,5 +1,6 @@
package ru.bvn13.voidforum.controllers.admin;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import ru.bvn13.voidforum.forms.PostForm;
import ru.bvn13.voidforum.models.Post;
import ru.bvn13.voidforum.models.User;
@ -10,6 +11,7 @@ import ru.bvn13.voidforum.services.CommentService;
import ru.bvn13.voidforum.services.PostService;
import ru.bvn13.voidforum.services.PrivilegeService;
import ru.bvn13.voidforum.services.UserService;
import ru.bvn13.voidforum.support.web.MessageHelper;
import ru.bvn13.voidforum.utils.DTOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@ -139,14 +141,9 @@ public class PostController {
@RequestMapping(value = "", method = POST)
public String create(Principal principal, @Valid PostForm postForm, Errors errors, Model model){
public String create(Principal principal, @Valid PostForm postForm, Errors errors, Model model, RedirectAttributes ra){
if (errors.hasErrors()) {
Map<String, WebError> webErrors = new HashMap<>();
errors.getAllErrors().forEach(e -> {
String field = ((FieldError)e).getField();
webErrors.put(field, new WebError(field, e.getDefaultMessage()));
});
model.addAttribute("errors", webErrors);
MessageHelper.addNamedErrorsAsList(ra, "errors", "Please check following errors:", errors);
return this.makeFormPostCreation(model, postForm);
} else {
Post post = DTOUtil.map(postForm, Post.class);
@ -161,14 +158,9 @@ public class PostController {
}
@RequestMapping(value = "{postId:[0-9]+}", method = {PUT, POST})
public String update(@PathVariable Long postId, @Valid PostForm postForm, Errors errors, Model model){
if (errors.hasErrors()){
Map<String, WebError> webErrors = new HashMap<>();
errors.getAllErrors().forEach(e -> {
String field = ((FieldError)e).getField();
webErrors.put(field, new WebError(field, e.getDefaultMessage()));
});
model.addAttribute("errors", webErrors);
public String update(@PathVariable Long postId, @Valid PostForm postForm, Errors errors, Model model, RedirectAttributes ra){
if (errors.hasErrors()) {
MessageHelper.addNamedErrorsAsList(ra, "errors", "Please check following errors:", errors);
return this.makeFormPostEdition(postId, model, postForm);
} else {
Post post = postRepository.findOne(postId);

View File

@ -3,24 +3,25 @@ package ru.bvn13.voidforum.forms;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* bvn13 <mail4bvn@gmail.com>.
*/
@Data
public class RegistrationForm {
public class RegistrationForm implements Serializable {
@Email(message = "Email should be valid")
@NotBlank(message = "Specify your email")
private String username;
private String username = "";
@Min(value = 4, message = "Nickname should be longer than 4 characters")
@Pattern(regexp = "\\w", message = "Nickname must not contain spaces")
@NotBlank(message = "Specify your nickname")
private String nickname;
private String nickname = "";
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$", message = "Password should be longer than 6 characters, at least one latin letter and one number")
@NotBlank(message = "Specify your password")
private String password;

View File

@ -13,7 +13,7 @@ import java.util.List;
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findAllByPostOrderById(Post post);
Page<Comment> findAllByPostOrderById(Post post, Pageable pageable);
List<Comment> findAllByPostAndParentCommentOrderById(Post post, Comment parentComment);
Page<Comment> findAllByPostAndDeletedMarkOrderById(Post post, Boolean deletedMark, Pageable pageable);
@ -21,4 +21,7 @@ public interface CommentRepository extends JpaRepository<Comment, Long> {
@Query("SELECT COUNT(c.id) FROM Comment AS c WHERE c.post = :post AND c.deletedMark = :deletedMark")
Integer getCommentsCountByPostAndDeletedMark(@Param("post") Post post, @Param("deletedMark") Boolean deletedMark);
@Query("SELECT COUNT(c.id) FROM Comment AS c WHERE c.post = :post")
Integer getCommentsCountByPost(@Param("post") Post post);
}

View File

@ -52,12 +52,21 @@ public class CommentService {
Page<Comment> availableComments = commentRepository.findAllByPostAndDeletedMarkOrderById(post, false, new PageRequest(page, pageSize, Sort.Direction.ASC, "createdAt"));
return availableComments;
}
public Page<Comment> getCommentsForPostForAdmin(Post post, int page, int pageSize) {
Page<Comment> availableComments = commentRepository.findAllByPostOrderById(post, new PageRequest(page, pageSize, Sort.Direction.ASC, "createdAt"));
return availableComments;
}
public Integer getLastPageCommentsForPost(Post post, int pageSize) {
Integer count = commentRepository.getCommentsCountByPostAndDeletedMark(post, false);
return (int) Math.ceil(count.intValue() / pageSize);
}
public Integer getLastPageCommentsForPostForAdmin(Post post, int pageSize) {
Integer count = commentRepository.getCommentsCountByPost(post);
return (int) Math.ceil(count.intValue() / pageSize);
}
public List<Comment> filterListByParentComment(List<Comment> comments, Comment parent) {
List<Comment> children = new ArrayList<>();
comments.forEach(c -> {

View File

@ -6,6 +6,7 @@ import org.hibernate.SessionFactory;
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
import ru.bvn13.voidforum.error.EmailExistsException;
import ru.bvn13.voidforum.error.NicknameExistsException;
import ru.bvn13.voidforum.models.Post;
import ru.bvn13.voidforum.models.Privilege;
import ru.bvn13.voidforum.models.User;
import ru.bvn13.voidforum.models.Role;
@ -68,13 +69,15 @@ public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
public UserDetails loadUserByUsername(String emailOrNickname) throws UsernameNotFoundException {
org.springframework.security.core.userdetails.User springUser = null;
//sessionFactory.openSession();
User user = userRepository.findByEmail(emailOrNickname);
if (user == null) {
user = userRepository.findByNickname(emailOrNickname);
}
User user = userRepository.findByEmail(email);
if (user == null) {
springUser = new org.springframework.security.core.userdetails.User(
"", "", true, true, true, true,
@ -90,8 +93,6 @@ public class UserService implements UserDetailsService {
);
}
//sessionFactory.close();
return springUser;
}
@ -166,9 +167,14 @@ public class UserService implements UserDetailsService {
return this.currentUserHasPrivilege(PrivilegeService.PRIVILEGE_WRITE);
}
public Boolean currentUserCanWriteCommentToPost(Post post) {
User user = currentUser();
return hasPrivilege(user, PrivilegeService.PRIVILEGE_OWNER)
|| post.getUser().getId().equals(user.getId()) ? false : post.getDisableCommenting();
}
public User registerNewUserAccount(User user) throws EmailExistsException, NicknameExistsException {
if (emailExist(user.getEmail())) {
throw new EmailExistsException("There is an account with that email address: " + user.getEmail());
}

View File

@ -1,5 +1,9 @@
package ru.bvn13.voidforum.support.web;
import ru.bvn13.voidforum.models.support.WebError;
import java.util.List;
/**
* A message to be displayed in web context. Depending on the type, different style will be applied.
*/
@ -43,4 +47,8 @@ public class Message implements java.io.Serializable {
public Object[] getArgs() {
return args;
}
public List<WebError> getArgsAsWebErrorList() {
return (List<WebError>) args[0];
}
}

View File

@ -1,7 +1,16 @@
package ru.bvn13.voidforum.support.web;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import ru.bvn13.voidforum.models.support.WebError;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static ru.bvn13.voidforum.support.web.Message.MESSAGE_ATTRIBUTE;
@ -90,4 +99,14 @@ public final class MessageHelper {
private static void addNamedAttribute(Model model, String name, String message, Message.Type type, Object... args) {
model.addAttribute(name, new Message(message, type, args));
}
public static void addNamedErrorsAsList(RedirectAttributes ra, String name, String message, Errors errors) {
List<WebError> webErrors = new ArrayList<>();
errors.getAllErrors().forEach(e -> {
String field = ((FieldError)e).getField();
webErrors.add(new WebError(field, e.getDefaultMessage()));
});
addNamedErrorAttribute(ra, name, message, webErrors);
}
}

View File

@ -12,28 +12,16 @@
<jmxConfigurator />
<logger name="ru.bvn13.voidforum" level="DEBUG">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ru.bvn13.voidforum" level="DEBUG"/>
<logger name="org.hibernate.SQL" level="WARN">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.hibernate.SQL" level="WARN"/>
<!--<logger name="org.hibernate.type" level="TRACE">-->
<!--<appender-ref ref="STDOUT" />-->
<!--</logger>-->
<!--<logger name="org.hibernate.type" level="TRACE"/>-->
<logger name="org.springframework.web" level="DEBUG">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.springframework.web" level="DEBUG"/>
<logger name="ru.bvn13.voidforum.controllers" level="DEBUG">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ru.bvn13.voidforum.admin.controllers" level="DEBUG">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ru.bvn13.voidforum.controllers" level="DEBUG"/>
<logger name="ru.bvn13.voidforum.admin.controllers" level="DEBUG"/>
<root level="WARN">
<appender-ref ref="STDOUT" />

View File

@ -45,7 +45,8 @@ img {
}
.markdown-body img {
width: 100%;
display: block;
max-width: 100%;
height: auto;
}
@ -199,6 +200,7 @@ body {
}
.comment-header ul li {
margin-left: -40px;
float: left;
padding-right: 10px;
display: inline;
@ -208,7 +210,3 @@ body {
float: right;
display: inline;
}
.btn {
color: #fff !important;
}

View File

@ -11,9 +11,9 @@ block content
if errors != null
.alert.alert-danger
span Check out errors!
= errors.getMessage()
ol
for err in errors.values()
for err in errors.getArgsAsWebErrorList()
li #{err.getField()} : #{err.getErrorMessage()}
form.post-form(method="post", action="/admin/posts/#{post.getId()}")

View File

@ -11,9 +11,9 @@ block content
if errors != null
.alert.alert-danger
span Check out errors!
= errors.getMessage()
ol
for err in errors.values()
for err in errors.getArgsAsWebErrorList()
li #{err.getField()} : #{err.getErrorMessage()}
form.post-form(method="post", action="/admin/posts")

View File

@ -1,9 +1,31 @@
.panel.panel-warning
.panel-heading.comment-header
ul
li
b
i
| DELETED:
b #{comment.getUser().getNickname()}
| , #{viewHelper.getFormattedDate(comment.getCreatedAt())}
li.admin
if userService.isCurrentUserAdmin()
.operations
a.btn.btn-xs.btn-danger.btn-delete(href="javascript:deleteComment(#{post.id}, #{comment.getId()})", postId="#{post.id}")
i.fa.fa-trash-o
form(id="form-#{post.getId()}-comment-#{comment.getId()}-delete",style="visibility: hidden", method="post", action="/admin/comments/#{comment.getId()}/delete")
input(type="hidden", name='_csrf', value='#{_csrf.token}')
input(type="hidden", name='postId', value='#{post.getId()}')
div(class="clearfix")
.panel-body
| Sometimes there was a comment here...
//if commentService.childrenCount(comments, comment) > 0
// .comment-children(style="padding-left: #{5 * commentService.childrenCount(comments, comment)}px")
// include list

View File

@ -7,7 +7,7 @@
|, #{viewHelper.getFormattedDate(comment.getCreatedAt())}
li.admin
if userService.isCurrentUserAdmin()
.td
.operations
a.btn.btn-xs.btn-danger.btn-delete(href="javascript:deleteComment(#{post.id}, #{comment.getId()})", postId="#{post.id}")
i.fa.fa-trash-o

View File

@ -10,14 +10,21 @@ block content
if error != null && !error.isEmpty()
div.alert.alert-danger
= error
if errors != null
div.alert.alert-danger
= errors.getMessage()
ol
for err in errors.getArgsAsWebErrorList()
li #{err.getErrorMessage()}
.row
.col-md-4.col-md-offset-4.login-container
.login-panel
form.signin-form(method="post",action="/register")
input(type="hidden", name='_csrf', value='#{_csrf.token}')
input.form-control(type="text",name="username",placeholder="Email")
input.form-control(type="text",name="nickname",placeholder="Nickname")
input.form-control(type="text",name="username",placeholder="Email", value="#{registrationForm.getUsername()}")
input.form-control(type="text",name="nickname",placeholder="Nickname", value="#{registrationForm.getNickname()}")
input.form-control(type="password",name="password",placeholder="Password")
input.form-control(type="password",name="passwordCheck",placeholder="Verify password")
button.btn.btn-primary.btn-block(type="submit") Register

View File

@ -9,7 +9,7 @@ block content
.login-panel
form.signin-form(method="post",action="/authenticate")
input(type="hidden", name='_csrf', value='#{_csrf.token}')
input.form-control(type="text",name="username",placeholder="Email")
input.form-control(type="text",name="username",placeholder="Email or Nickname")
input.form-control(type="password",name="password",placeholder="Password")
div.col-centered(style="padding-top: 10px;")
input(type="checkbox",name="remember-me")