implemented comments management (deletion/undeletion) by admins. implemented localization. working on localization: register an singin forms are now localized on EN and RU

master
Vyacheslav N. Boyko 2017-12-14 10:23:47 +03:00
parent 188fe4fbcb
commit b188319494
20 changed files with 351 additions and 52 deletions

View File

@ -48,7 +48,7 @@ configurations {
dependencies {
// --- spring boot ---
//compile('org.springframework.boot:spring-boot-starter-cache')
compile('org.springframework.boot:spring-boot-starter-cache')
compile('org.springframework.boot:spring-boot-starter-data-jpa:1.5.10.BUILD-SNAPSHOT')
compile('org.springframework.boot:spring-boot-starter-security:1.5.10.BUILD-SNAPSHOT')
compile('org.springframework.session:spring-session:1.3.1.RELEASE')

View File

@ -6,8 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import redis.embedded.Redis;
import redis.embedded.RedisServer;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -19,6 +19,7 @@ import static java.util.stream.Collectors.joining;
*/
@SpringBootApplication
@EnableCaching
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);

View File

@ -10,6 +10,7 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import ru.bvn13.voidforum.support.localization.LocalizationHelper;
import ru.bvn13.voidforum.support.web.ViewHelperVF;
import javax.annotation.PostConstruct;
@ -29,9 +30,13 @@ public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private Environment env;
//@Autowired
//private LocalizationHelper localizationHelper;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(viewObjectAddingInterceptor());
//registry.addInterceptor(localizationHelper.localeChangeInterceptor());
super.addInterceptors(registry);
}

View File

@ -1,33 +1,20 @@
package ru.bvn13.voidforum.controllers;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import ru.bvn13.voidforum.error.EmailExistsException;
import ru.bvn13.voidforum.error.NicknameExistsException;
import ru.bvn13.voidforum.forms.RegistrationForm;
import ru.bvn13.voidforum.models.User;
import ru.bvn13.voidforum.models.support.AppLocale;
import ru.bvn13.voidforum.services.UserService;
import ru.bvn13.voidforum.support.web.Message;
import ru.bvn13.voidforum.support.localization.SessionStorer;
import ru.bvn13.voidforum.support.web.MessageHelper;
import ru.bvn13.voidforum.utils.DTOUtil;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.security.Principal;
@ -40,25 +27,30 @@ public class UserController {
@Autowired
private UserService userService;
@Autowired
private SessionStorer sessionStorer;
@RequestMapping(value = "signin", method = RequestMethod.GET)
public String signin(Principal principal, RedirectAttributes ra) {
public String signin(@RequestParam(defaultValue = "en") String lang, Principal principal, RedirectAttributes ra) {
sessionStorer.setLocale(AppLocale.valueOfOrDefault(lang.toUpperCase()));
return principal == null ? "users/signin" : "redirect:/";
}
@GetMapping(value = "register")
public String registrationForm(Model model) {
public String registrationForm(@RequestParam(defaultValue = "en") String lang, Model model) {
if (!model.containsAttribute("registrationForm")) {
// it contains it after post request when errors occurred and was redirected
model.addAttribute("registrationForm", new RegistrationForm());
}
sessionStorer.setLocale(AppLocale.valueOfOrDefault(lang.toUpperCase()));
return "users/register";
}
@PostMapping(value = "register")
public String register(@Valid RegistrationForm registrationForm, Errors errors, Model model, RedirectAttributes ra) {
public String register(@RequestParam(defaultValue = "en") String lang, @Valid RegistrationForm registrationForm, Errors errors, Model model, RedirectAttributes ra) {
if (errors.hasErrors()) {
MessageHelper.addNamedErrorsAsList(ra, "errors", "Please check errors:", errors);
@ -81,10 +73,13 @@ public class UserController {
return "redirect:/register";
}
AppLocale locale = AppLocale.valueOfOrDefault(lang.toUpperCase());
User user = new User();
user.setEmail(registrationForm.getUsername());
user.setNickname(registrationForm.getNickname());
user.setPassword(registrationForm.getPassword());
user.setLocale(locale);
//DTOUtil.mapTo(registrationForm, user);
try {

View File

@ -1,17 +1,19 @@
package ru.bvn13.voidforum.controllers.admin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.*;
import ru.bvn13.voidforum.error.NotFoundException;
import ru.bvn13.voidforum.forms.CommentDeletionForm;
import ru.bvn13.voidforum.forms.CommentForm;
import ru.bvn13.voidforum.models.Comment;
import ru.bvn13.voidforum.models.Post;
import ru.bvn13.voidforum.services.CommentService;
import ru.bvn13.voidforum.services.PostService;
import ru.bvn13.voidforum.utils.DTOUtil;
import javax.validation.Valid;
@ -34,6 +36,17 @@ public class CommentController {
private PostService postService;
@GetMapping(value = "/{commentId:[\\d]+}", produces= MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody CommentForm getComment(@PathVariable Long commentId) {
Comment comment = commentService.getCommentById(commentId);
if (comment == null) {
throw new NotFoundException("Comment not found");
}
CommentForm commentForm = new CommentForm();
DTOUtil.mapTo(comment, commentForm);
return commentForm;
}
@RequestMapping(value = "/{commentId:[\\d]+}/delete", method = {POST})
public String deleteComment(@PathVariable Long commentId, @Valid CommentDeletionForm form, Errors errors, Model model) throws Exception {
if (errors.hasErrors()) {

View File

@ -8,12 +8,13 @@ import ru.bvn13.voidforum.models.support.CommentFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* Created by bvn13 on 08.12.2017.
*/
@Data
public class CommentForm {
public class CommentForm implements Serializable {
@NotNull
private Long postId;
@ -23,6 +24,8 @@ public class CommentForm {
@NotEmpty
private String content;
private String renderedContent;
@NotNull
private CommentFormat commentFormat = CommentFormat.MARKDOWN;

View File

@ -3,6 +3,7 @@ package ru.bvn13.voidforum.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import ru.bvn13.voidforum.models.support.AppLocale;
import ru.bvn13.voidforum.services.RoleService;
import ru.bvn13.voidforum.services.UserService;
@ -47,6 +48,9 @@ public class User extends BaseModel {
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.REMOVE)
private Collection<Comment> comments = new ArrayList<>();
@Column
private AppLocale locale;
public User() {
}

View File

@ -0,0 +1,37 @@
package ru.bvn13.voidforum.models.support;
/**
* Created by bvn13 on 12.12.2017.
*/
public enum AppLocale {
EN("en"),
RU("ru");
private String name;
AppLocale(String name){
this.name = name;
}
public String getName(){
return name;
}
public String getId() {
return name();
}
@Override
public String toString() {
return getName();
}
public static AppLocale valueOfOrDefault(String name) {
try {
return AppLocale.valueOf(name);
} catch (IllegalArgumentException iae) {
return AppLocale.EN;
}
}
}

View File

@ -1,6 +1,8 @@
package ru.bvn13.voidforum.services;
import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
@ -10,6 +12,7 @@ import ru.bvn13.voidforum.models.Post;
import ru.bvn13.voidforum.models.Privilege;
import ru.bvn13.voidforum.models.User;
import ru.bvn13.voidforum.models.Role;
import ru.bvn13.voidforum.models.support.AppLocale;
import ru.bvn13.voidforum.models.support.PostFormat;
import ru.bvn13.voidforum.repositories.PrivilegeRepository;
import ru.bvn13.voidforum.repositories.UserRepository;

View File

@ -0,0 +1,124 @@
package ru.bvn13.voidforum.support.localization;
import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import ru.bvn13.voidforum.models.User;
import ru.bvn13.voidforum.models.support.AppLocale;
import ru.bvn13.voidforum.services.UserService;
import ru.bvn13.voidforum.utils.CommonHelper;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.*;
/**
* Created by bvn13 on 12.12.2017.
*/
@JadeHelper("locale")
@Component
public class LocalizationHelper {
private static Logger logger = LoggerFactory.getLogger(LocalizationHelper.class);
private AppLocale defaultLocale = AppLocale.EN;
private static Map<AppLocale, Map<String, String>> messages = new HashMap<>();
private static Map<AppLocale, String> resources = new HashMap<>();
@Autowired
private UserService userService;
@Autowired
private SessionStorer sessionStorer;
static {
resources.put(AppLocale.EN, "i18n/messages.properties");
resources.put(AppLocale.RU, "i18n/messages_ru.properties");
}
@PostConstruct
private void init() {
for (AppLocale locale : resources.keySet()) {
loadLocale(locale, resources.get(locale));
}
}
private static void loadLocale(AppLocale locale, String resourceName) {
Map<String, String> resourceMessages = new HashMap<>();
Resource cpr = new ClassPathResource(resourceName);
try {
File file = cpr.getFile();
BufferedReader b = new BufferedReader(new FileReader(file));
String readLine = "";
while ((readLine = b.readLine()) != null) {
parseResourceLine(readLine, resourceMessages);
}
messages.put(locale, resourceMessages);
} catch (IOException e) {
logger.error("Could not load resource: "+resourceName);
e.printStackTrace();
return;
}
}
private static void parseResourceLine(String line, Map<String, String> resourceMessages) throws IllegalArgumentException {
if (line.trim().isEmpty() || line.trim().substring(0,1).equals("#")) {
return;
}
String[] args = line.split("=");
if (args.length != 2) {
throw new IllegalArgumentException("Wrong locale resource: "+line);
}
resourceMessages.put(args[0].trim(), args[1].trim());
}
public String msg(String msgId) {
AppLocale locale = getCurrentLocale();
String message = "";
if (messages.containsKey(locale) && messages.get(locale).containsKey(msgId)) {
message = messages.get(locale).get(msgId);
} else {
message = messages.get(defaultLocale).get(msgId);
}
return message;
}
public List<AppLocale> getSupportedLocales() {
return CommonHelper.asSortedList(messages.keySet());
}
public AppLocale getCurrentLocale() {
User user = userService.currentUser();
AppLocale locale = null;
if (user != null) {
locale = user.getLocale();
}
if (locale == null) {
locale = sessionStorer.getLocale();
}
if (locale == null) {
locale = defaultLocale;
}
return locale;
}
}

View File

@ -0,0 +1,23 @@
package ru.bvn13.voidforum.support.localization;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import ru.bvn13.voidforum.models.support.AppLocale;
/**
* Created by bvn13 on 14.12.2017.
*/
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class SessionStorer {
@Getter
@Setter
private AppLocale locale;
}

View File

@ -0,0 +1,19 @@
package ru.bvn13.voidforum.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Created by bvn13 on 14.12.2017.
*/
public class CommonHelper {
public static
<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
}

View File

@ -1,7 +1,21 @@
view.index.title=Home page
signup.success=Congratulations {0}! You have successfully signed up.
signin.already=You have already signed in.
language=Language
# sign in form
signin.header=Sign in
signin.form.email=Email or Nickname
signin.form.password=Password
signin.form.rememberme=Remember me
signin.form.login=Login
signin.form.register=Register
# registration form
register.header=Sign up
register.form.email=Email
register.form.nickname=Nickname
register.form.password=Password
register.form.password-verify=Verify password
register.form.register=Register
register.form.login=Sign in
# Validation messages
notBlank.message = The value may not be empty!
email.message = The value must be a valid email!

View File

@ -0,0 +1,20 @@
language=Язык
# sign in form
signin.header=Вход
signin.form.email=Е-Почта или Никнейм
signin.form.password=Пароль
signin.form.rememberme=Запомнить меня
signin.form.login=Войти
signin.form.register=Регистрация
# registration form
register.header=Регистрация
register.form.email=Е-Почта
register.form.nickname=Никнейм
register.form.password=Пароль
register.form.password-verify=Повторите пароль
register.form.register=Зарегистрироваться
register.form.login=Вход

View File

@ -171,6 +171,9 @@ img.borderless {
color: white;
margin-left: 5px;
}
.operations a.btn-default {
color: #333;
}
.post-status {
float: left;
color: red;

View File

@ -12,6 +12,9 @@
li.admin
if userService.isCurrentUserAdmin()
.operations
a.btn.btn-xs.btn-info(href="javascript:showComment(#{comment.getId()})")
i.fa.fa-eye
a.btn.btn-xs.btn-danger.btn-delete(href="javascript:deleteComment(#{post.id}, #{comment.getId()})", postId="#{post.id}")
i.fa.fa-trash-o
@ -21,12 +24,8 @@
div(class="clearfix")
.panel-body
div.panel-body(id="comment-#{comment.getId()}")
| 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

@ -21,4 +21,17 @@ script
}
}
if userService.isCurrentUserAdmin()
script
:javascript
function showComment(commentId) {
$.ajax({
url: "/admin/comments/" + commentId,
type: "GET",
contentType: "application/json"
}).done(function (data) {
$("#comment-"+commentId).html(data.renderedContent);
});
}

View File

@ -1,5 +1,5 @@
.container
.header-title
.header-title-wrapper
.text-left
block page_title
//.text-left
block page_title

View File

@ -1,7 +1,7 @@
extends ../layout/app
block page_title
h1 Registration
h1 #{locale.msg("register.header")}
block content
@ -18,14 +18,26 @@ block content
li #{err.getErrorMessage()}
.row
.col-md-4.col-md-offset-4.login-container
span #{locale.msg("language")}
span.operations
for loc in locale.getSupportedLocales()
if loc != locale.getCurrentLocale()
a.btn.btn-default(href="/register?lang=#{loc.getId()}") #{loc.getName()}
else
a.btn.btn-primary(href="/register?lang=#{loc.getId()}") #{loc.getName()}
.row
.col-md-4.col-md-offset-4.login-container
.login-panel
form.signin-form(method="post",action="/register")
form.signin-form(method="post",action="/register?lang=#{locale.getCurrentLocale().getId()}")
input(type="hidden", name='_csrf', value='#{_csrf.token}')
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
a.btn.btn-default.btn-block(href="/signin") Login
input.form-control(type="text",name="username",placeholder='#{locale.msg("register.form.email")}', value="#{registrationForm.getUsername()}")
input.form-control(type="text",name="nickname",placeholder='#{locale.msg("register.form.nickname")}', value="#{registrationForm.getNickname()}")
input.form-control(type="password",name="password",placeholder='#{locale.msg("register.form.password")}')
input.form-control(type="password",name="passwordCheck",placeholder='#{locale.msg("register.form.password-verify")}')
button.btn.btn-primary.btn-block(type="submit") #{locale.msg("register.form.register")}
a.btn.btn-default.btn-block(href="/signin?lang=#{locale.getCurrentLocale().getId()}") #{locale.msg("register.form.login")}

View File

@ -1,20 +1,31 @@
extends ../layout/app
block page_title
h1 Sign in
h1 #{locale.msg("signin.header")}
block content
.row
.col-md-4.col-md-offset-4.login-container
span #{locale.msg("language")}
span.operations
for loc in locale.getSupportedLocales()
if loc != locale.getCurrentLocale()
a.btn.btn-default(href="/signin?lang=#{loc.getId()}") #{loc.getName()}
else
a.btn.btn-primary(href="/signin?lang=#{loc.getId()}") #{loc.getName()}
.row
.col-md-4.col-md-offset-4.login-container
.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 or Nickname")
input.form-control(type="password",name="password",placeholder="Password")
input.form-control(type="text",name="username",placeholder='#{locale.msg("signin.form.email")}')
input.form-control(type="password",name="password",placeholder='#{locale.msg("signin.form.password")}')
div.col-centered(style="padding-top: 10px;")
input(type="checkbox",name="remember-me")
span(style="padding-left: 10px")
| remember me
button.btn.btn-primary.btn-block(type="submit") Login
a.btn.btn-default.btn-block(href="/register") Register
| #{locale.msg("signin.form.rememberme")}
button.btn.btn-primary.btn-block(type="submit") #{locale.msg("signin.form.login")}
a.btn.btn-default.btn-block(href="/register?lang=#{locale.getCurrentLocale().getId()}") #{locale.msg("signin.form.register")}