Compare commits

...

7 Commits

47 changed files with 340 additions and 361 deletions

View File

@@ -8,7 +8,6 @@ import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication @SpringBootApplication
@EnableAutoConfiguration @EnableAutoConfiguration
public class CodikiApplication { public class CodikiApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(CodikiApplication.class, args); SpringApplication.run(CodikiApplication.class, args);
} }

View File

@@ -101,8 +101,8 @@ public class AccountController {
} }
@PutMapping("/") @PutMapping("/")
public void update(@RequestBody final UserDTO pUser, final HttpServletRequest pRequest, public void update(@RequestBody UserDTO pUser, HttpServletResponse pResponse, Principal pPrincipal)
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { throws IOException {
accountService.updateUser(pUser, pRequest, pResponse, pPrincipal); accountService.updateUser(pUser, pResponse, pPrincipal);
} }
} }

View File

@@ -1,17 +1,5 @@
package org.codiki.account; package org.codiki.account;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.security.Principal;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codiki.core.entities.dto.ImageDTO; import org.codiki.core.entities.dto.ImageDTO;
import org.codiki.core.entities.dto.PasswordWrapperDTO; import org.codiki.core.entities.dto.PasswordWrapperDTO;
import org.codiki.core.entities.dto.UserDTO; import org.codiki.core.entities.dto.UserDTO;
@@ -22,38 +10,30 @@ import org.codiki.core.security.CustomAuthenticationProvider;
import org.codiki.core.services.FileUploadService; import org.codiki.core.services.FileUploadService;
import org.codiki.core.services.UserService; import org.codiki.core.services.UserService;
import org.codiki.core.utils.StringUtils; import org.codiki.core.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service @Service
public class AccountService { public class AccountService {
/** Logger. */ private final CustomAuthenticationProvider authenticationProvider;
private static final Logger LOG = LoggerFactory.getLogger(FileUploadService.class); private final UserService userService;
private final UserRepository userRepository;
private final FileUploadService fileUploadService;
private final ImageRepository imageRepository;
private CustomAuthenticationProvider authenticationProvider; /** Constructor. */
private UserService userService;
private UserRepository userRepository;
private FileUploadService fileUploadService;
private ImageRepository imageRepository;
/**
* Constructor.
* @param authenticationProvider
* @param userService
* @param userRepository
* @param fileUploadService
* @param imageRepository
*/
public AccountService(CustomAuthenticationProvider authenticationProvider, public AccountService(CustomAuthenticationProvider authenticationProvider,
UserService userService, UserService userService,
UserRepository userRepository, UserRepository userRepository,
@@ -93,8 +73,8 @@ public class AccountService {
return resultCode; return resultCode;
} }
public String uploadFile(final MultipartFile pFile, final HttpServletRequest pRequest, public String uploadFile(MultipartFile pFile, HttpServletResponse pResponse, Principal pPrincipal)
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { throws IOException {
final String avatarFileName = fileUploadService.uploadProfileImage(pFile); final String avatarFileName = fileUploadService.uploadProfileImage(pFile);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal); final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
@@ -117,8 +97,7 @@ public class AccountService {
return fileUploadService.loadAvatar(pAvatarFileName); return fileUploadService.loadAvatar(pAvatarFileName);
} }
public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse, public List<ImageDTO> getUserImages(HttpServletResponse pResponse, Principal pPrincipal) throws IOException {
final Principal pPrincipal) throws IOException {
List<ImageDTO> result = new LinkedList<>(); List<ImageDTO> result = new LinkedList<>();
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal); final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
@@ -132,7 +111,7 @@ public class AccountService {
return result; return result;
} }
public void signin(final User pUser, final HttpServletResponse pResponse) throws IOException { public void signin(User pUser, HttpServletResponse pResponse) throws IOException {
if(pUser.getName() == null || pUser.getEmail() == null || pUser.getPassword() == null || "".equals(pUser.getPassword().trim())) { if(pUser.getName() == null || pUser.getEmail() == null || pUser.getPassword() == null || "".equals(pUser.getPassword().trim())) {
pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST); pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
} else if(userRepository.findByEmail(pUser.getEmail()).isPresent()) { } else if(userRepository.findByEmail(pUser.getEmail()).isPresent()) {
@@ -148,8 +127,7 @@ public class AccountService {
} }
} }
public void updateUser(final UserDTO pUser, final HttpServletRequest pRequest, public void updateUser(UserDTO pUser, HttpServletResponse pResponse, Principal pPrincipal) throws IOException {
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Optional<User> connectedUserOpt = userService.getUserByPrincipal(pPrincipal); final Optional<User> connectedUserOpt = userService.getUserByPrincipal(pPrincipal);
if(connectedUserOpt.isPresent()) { if(connectedUserOpt.isPresent()) {
final User connectedUser = connectedUserOpt.get(); final User connectedUser = connectedUserOpt.get();

View File

@@ -1,11 +1,7 @@
package org.codiki.categories; package org.codiki.categories;
import java.util.List; import com.fasterxml.jackson.annotation.JsonView;
import java.util.Optional; import org.codiki.core.entities.dto.View;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.codiki.core.entities.dto.CategoryDTO;
import org.codiki.core.entities.persistence.Category; import org.codiki.core.entities.persistence.Category;
import org.codiki.core.repositories.CategoryRepository; import org.codiki.core.repositories.CategoryRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -14,22 +10,33 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController @RestController
@RequestMapping("/api/categories") @RequestMapping("/api/categories")
public class CategoryController { public class CategoryController {
private final CategoryRepository categoryRepository;
@Autowired /** Constructor. */
private CategoryRepository categoryRepository; public CategoryController(CategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
@GetMapping("/{id}")
public CategoryDTO findById(@PathVariable("id") final Long pId) {
final Optional<Category> result = categoryRepository.findById(pId);
return result.isPresent() ? new CategoryDTO(result.get()) : null;
} }
@JsonView(View.CategoryDTO.class)
@GetMapping("/{id}")
public Category findById(@PathVariable("id") final Long pId,
HttpServletResponse response) {
return categoryRepository.findByIdWithSubCategories(pId)
.orElseGet(() -> {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return null;
});
}
@JsonView(View.CategoryDTO.class)
@GetMapping("/") @GetMapping("/")
public List<CategoryDTO> getAll() { public List<Category> getAll() {
return StreamSupport.stream(categoryRepository.findAll().spliterator(), false) return categoryRepository.findAllWithSubCategories();
.map(CategoryDTO::new).collect(Collectors.toList());
} }
} }

View File

@@ -1,58 +0,0 @@
package org.codiki.core.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* Controller that catch errors from spring rest or spring security and others, and transform them to JSON response.
*/
@RestController
@RequestMapping("/error")
public class CustomErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
@Autowired
public CustomErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping
public Map<String, Object> error(HttpServletRequest request){
Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));
String trace = (String) body.get("trace");
if(trace != null){
String[] lines = trace.split("\n\t");
body.put("trace", lines);
}
return body;
}
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
if (parameter == null) {
return false;
}
return !"false".equals(parameter.toLowerCase());
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
return errorAttributes.getErrorAttributes(new ServletWebRequest(request), includeStackTrace);
}
}

View File

@@ -17,21 +17,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableJpaRepositories("org.codiki.core.repositories") @EnableJpaRepositories("org.codiki.core.repositories")
@PropertySource("classpath:application.yml") @PropertySource("classpath:application.yml")
public class JpaConfiguration { public class JpaConfiguration {
@Value("${spring.jpa.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.jpa.datasource.url}")
private String url;
@Value("${spring.jpa.datasource.username}")
private String username;
@Value("${spring.jpa.datasource.password}")
private String password;
@Bean(name = "dataSource") @Bean(name = "dataSource")
public DataSource getDataSource() { public DataSource getDataSource(@Value("${spring.jpa.datasource.driverClassName}") String driverClassName,
@Value("${spring.jpa.datasource.url}") String url,
@Value("${spring.jpa.datasource.username}") String username,
@Value("${spring.jpa.datasource.password}") String password) {
return DataSourceBuilder.create() return DataSourceBuilder.create()
.username(username) .username(username)
.password(password) .password(password)

View File

@@ -10,7 +10,6 @@ import java.io.IOException;
@RestController @RestController
public class RobotsTxtController { public class RobotsTxtController {
private static final Logger LOG = LoggerFactory.getLogger(RobotsTxtController.class); private static final Logger LOG = LoggerFactory.getLogger(RobotsTxtController.class);
@RequestMapping(value = "/robots.txt") @RequestMapping(value = "/robots.txt")

View File

@@ -3,4 +3,5 @@ package org.codiki.core.entities.dto;
public class View { public class View {
public interface UserDTO {} public interface UserDTO {}
public interface PostDTO {} public interface PostDTO {}
public interface CategoryDTO {}
} }

View File

@@ -20,10 +20,12 @@ import org.codiki.core.entities.dto.CategoryDTO;
import org.codiki.core.entities.dto.View; import org.codiki.core.entities.dto.View;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.hibernate.annotations.Proxy;
@Entity @Entity
@Table(name="category") @Table(name="category")
@Inheritance(strategy = InheritanceType.JOINED) @Inheritance(strategy = InheritanceType.JOINED)
@Proxy(lazy = false)
public class Category implements Serializable { public class Category implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -32,10 +34,10 @@ public class Category implements Serializable {
/* ******************* */ /* ******************* */
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonView(View.PostDTO.class) @JsonView({View.PostDTO.class, View.CategoryDTO.class})
private Long id; private Long id;
@JsonView(View.PostDTO.class) @JsonView({View.PostDTO.class, View.CategoryDTO.class})
private String name; private String name;
/* ******************* */ /* ******************* */
@@ -45,6 +47,7 @@ public class Category implements Serializable {
@JoinColumn(name = "creator_id") @JoinColumn(name = "creator_id")
protected User creator; protected User creator;
@JsonView({View.CategoryDTO.class})
@OneToMany(mappedBy = "mainCategory") @OneToMany(mappedBy = "mainCategory")
private List<SubCategory> listSubCategories; private List<SubCategory> listSubCategories;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -19,6 +21,7 @@ import javax.persistence.TemporalType;
@Entity @Entity
@Table(name="comment") @Table(name="comment")
@Proxy(lazy = false)
public class Comment implements Serializable { public class Comment implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@@ -17,6 +19,7 @@ import javax.persistence.TemporalType;
@Entity @Entity
@Table(name="comment_history") @Table(name="comment_history")
@Proxy(lazy = false)
public class CommentHistory implements Serializable { public class CommentHistory implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@@ -16,6 +18,7 @@ import javax.persistence.TemporalType;
@Entity @Entity
@Table(name="image") @Table(name="image")
@Proxy(lazy = false)
public class Image implements Serializable { public class Image implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -25,9 +25,11 @@ import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.GenerationTime;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.hibernate.annotations.Proxy;
@Entity @Entity
@Table(name="post") @Table(name="post")
@Proxy(lazy = false)
public class Post implements Serializable { public class Post implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@@ -17,6 +19,7 @@ import javax.persistence.TemporalType;
@Entity @Entity
@Table(name="post_history") @Table(name="post_history")
@Proxy(lazy = false)
public class PostHistory implements Serializable { public class PostHistory implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -11,9 +11,11 @@ import javax.persistence.Table;
import org.codiki.core.entities.dto.View; import org.codiki.core.entities.dto.View;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.hibernate.annotations.Proxy;
@Entity @Entity
@Table(name="role") @Table(name="role")
@Proxy(lazy = false)
public class Role implements Serializable { public class Role implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,5 +1,8 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import com.fasterxml.jackson.annotation.JsonView;
import org.hibernate.annotations.Proxy;
import java.util.List; import java.util.List;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -10,6 +13,7 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name = "sub_category") @Table(name = "sub_category")
@Proxy(lazy = false)
public class SubCategory extends Category { public class SubCategory extends Category {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -34,4 +38,15 @@ public class SubCategory extends Category {
public List<Post> getListPosts() { public List<Post> getListPosts() {
return listPosts; return listPosts;
} }
/**
* {@inheritDoc Category#getListSubCategories}.
*
* This getter is overrided to set {@link JsonView} annotation to avoid Lazy initialization for subcategories.
*/
@JsonView()
@Override
public List<SubCategory> getListSubCategories() {
return super.getListSubCategories();
}
} }

View File

@@ -25,9 +25,11 @@ import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.GenerationTime;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.hibernate.annotations.Proxy;
@Entity @Entity
@Table(name="`user`") @Table(name="`user`")
@Proxy(lazy = false)
public class User implements Serializable { public class User implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -16,6 +18,7 @@ import javax.persistence.Table;
*/ */
@Entity @Entity
@Table(name="version") @Table(name="version")
@Proxy(lazy = false)
public class Version implements Serializable { public class Version implements Serializable {
private static final long serialVersionUID = -8803744005903941330L; private static final long serialVersionUID = -8803744005903941330L;

View File

@@ -1,5 +1,7 @@
package org.codiki.core.entities.persistence; package org.codiki.core.entities.persistence;
import org.hibernate.annotations.Proxy;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -13,6 +15,7 @@ import javax.persistence.Table;
@Entity @Entity
@Table(name="version_revision") @Table(name="version_revision")
@Proxy(lazy = false)
public class VersionRevision implements Serializable { public class VersionRevision implements Serializable {
private static final long serialVersionUID = 1837590467941917225L; private static final long serialVersionUID = 1837590467941917225L;

View File

@@ -1,14 +1,19 @@
package org.codiki.core.repositories; package org.codiki.core.repositories;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.codiki.core.entities.persistence.Category; import org.codiki.core.entities.persistence.Category;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface CategoryRepository extends CrudRepository<Category, Long> { public interface CategoryRepository extends CrudRepository<Category, Long> {
@Query("SELECT c FROM Category c JOIN FETCH c.listSubCategories") @Query("SELECT c FROM Category c LEFT JOIN FETCH c.listSubCategories WHERE c.id = :id")
Optional<Category> findByIdWithSubCategories(@Param("id") Long id);
@Query("SELECT DISTINCT(c) FROM Category c LEFT JOIN FETCH c.listSubCategories ORDER BY c.name")
List<Category> findAllWithSubCategories(); List<Category> findAllWithSubCategories();
} }

View File

@@ -19,7 +19,7 @@ import org.springframework.stereotype.Component;
public class CorsFilter implements Filter { public class CorsFilter implements Filter {
@Override @Override
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) {
// Do nothing // Do nothing
} }
@@ -40,5 +40,4 @@ public class CorsFilter implements Filter {
public void destroy() { public void destroy() {
// Do nothing // Do nothing
} }
} }

View File

@@ -1,23 +1,21 @@
package org.codiki.core.security; package org.codiki.core.security;
import java.util.ArrayList;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component @Component
public class CustomAuthenticationProvider implements AuthenticationProvider { public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// Creation of the authentication bean with its roles // Creation of the authentication bean with its roles
Authentication auth = new UsernamePasswordAuthenticationToken(authentication.getName(), Authentication auth = new UsernamePasswordAuthenticationToken(authentication.getName(),
authentication.getCredentials(), new ArrayList<GrantedAuthority>()); authentication.getCredentials(), new ArrayList<>());
// Set the auth bean in spring security context // Set the auth bean in spring security context
SecurityContextHolder.getContext().setAuthentication(auth); SecurityContextHolder.getContext().setAuthentication(auth);
@@ -29,5 +27,4 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class); return authentication.equals(UsernamePasswordAuthenticationToken.class);
} }
} }

View File

@@ -1,15 +1,13 @@
package org.codiki.core.security; package org.codiki.core.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** /**
* Authentication entry point configured in * Authentication entry point configured in
* {@link SecurityConfiguration#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)} * {@link SecurityConfiguration#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)}

View File

@@ -26,11 +26,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private CustomAuthenticationProvider authenticationProvider; private CustomAuthenticationProvider authenticationProvider;
private RestAuthenticationEntryPoint authenticationEntryPoint; private RestAuthenticationEntryPoint authenticationEntryPoint;
/** /** Constructor. */
* Constructor.
* @param authenticationProvider
* @param authenticationEntryPoint
*/
public SecurityConfiguration(CustomAuthenticationProvider authenticationProvider, public SecurityConfiguration(CustomAuthenticationProvider authenticationProvider,
RestAuthenticationEntryPoint authenticationEntryPoint) { RestAuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationProvider = authenticationProvider; this.authenticationProvider = authenticationProvider;

View File

@@ -1,15 +1,6 @@
package org.codiki.core.services; package org.codiki.core.services;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.codiki.core.utils.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -18,6 +9,12 @@ import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
@Service @Service
public class FileUploadService { public class FileUploadService {
/** Logger. */ /** Logger. */
@@ -25,12 +22,17 @@ public class FileUploadService {
/** Length of uploaded file names. */ /** Length of uploaded file names. */
private static final int DESTINATION_IMAGE_NAME_LENGTH = 30; private static final int DESTINATION_IMAGE_NAME_LENGTH = 30;
@Value("${codiki.files.upload}") private final String folderUpload;
private String folderUpload; private final String folderProfileImages;
@Value("${codiki.files.profile-images}") private final String folderImages;
private String folderProfileImages;
@Value("${codiki.files.images}") public FileUploadService(@Value("${codiki.files.upload}") String folderUpload,
private String folderImages; @Value("${codiki.files.profile-images}") String folderProfileImages,
@Value("${codiki.files.images}")String folderImages) {
this.folderUpload = folderUpload;
this.folderProfileImages = folderProfileImages;
this.folderImages = folderImages;
}
/** /**
* Builds the destination file name, which is a random string with 30 char * Builds the destination file name, which is a random string with 30 char

View File

@@ -9,7 +9,6 @@ import org.springframework.stereotype.Service;
@Service @Service
public class ParserService { public class ParserService {
private static final String REG_CODE = "\\[code lg=&quot;([a-z]+)&quot;\\](.*)\\[\\/code\\]\\n"; private static final String REG_CODE = "\\[code lg=&quot;([a-z]+)&quot;\\](.*)\\[\\/code\\]\\n";
private static final String REG_CODE_REPLACE = "<pre class=\"line-numbers\"><code class=\"language-\\1\">\\2</code></pre>"; private static final String REG_CODE_REPLACE = "<pre class=\"line-numbers\"><code class=\"language-\\1\">\\2</code></pre>";
private static final String REG_IMAGES = "\\[img src=&quot;([^\"| ]+)&quot;( alt=&quot;([^\"| ]+)&quot;)? \\/\\]"; private static final String REG_IMAGES = "\\[img src=&quot;([^\"| ]+)&quot;( alt=&quot;([^\"| ]+)&quot;)? \\/\\]";
@@ -17,12 +16,12 @@ public class ParserService {
private static final String REG_LINKS = "\\[link href=&quot;([^\"| ]+)&quot; txt=&quot;([^\"| ]+)&quot; \\/\\]"; private static final String REG_LINKS = "\\[link href=&quot;([^\"| ]+)&quot; txt=&quot;([^\"| ]+)&quot; \\/\\]";
private static final String REG_LINKS_REPLACE = "<a href=\"\\1\">\\2</a>"; private static final String REG_LINKS_REPLACE = "<a href=\"\\1\">\\2</a>";
static final Pattern PATTERN_CODE; private static final Pattern PATTERN_CODE;
static final Pattern PATTERN_CODE_REPLACE; private static final Pattern PATTERN_CODE_REPLACE;
static final Pattern PATTERN_IMAGES; private static final Pattern PATTERN_IMAGES;
static final Pattern PATTERN_IMAGES_REPLACE; private static final Pattern PATTERN_IMAGES_REPLACE;
static final Pattern PATTERN_LINKS; private static final Pattern PATTERN_LINKS;
static final Pattern PATTERN_LINKS_REPLACE; private static final Pattern PATTERN_LINKS_REPLACE;
static { static {
PATTERN_CODE = Pattern.compile(REG_CODE); PATTERN_CODE = Pattern.compile(REG_CODE);
@@ -45,7 +44,7 @@ public class ParserService {
return pSource.replace("&pound;&oslash;", "$"); return pSource.replace("&pound;&oslash;", "$");
} }
protected String parseHeaders(final String pSource) { String parseHeaders(final String pSource) {
String result = pSource; String result = pSource;
for(int i = 1 ; i <= 3 ; i++) { for(int i = 1 ; i <= 3 ; i++) {
result = parseHeadersHX(result, i); result = parseHeadersHX(result, i);
@@ -53,7 +52,7 @@ public class ParserService {
return result; return result;
} }
protected String parseHeadersHX(final String pSource, final int pNumHeader) { String parseHeadersHX(final String pSource, final int pNumHeader) {
String result = pSource; String result = pSource;
// (.*)(\[hX\](.*)\[\/hX\])+(.*) // (.*)(\[hX\](.*)\[\/hX\])+(.*)
@@ -73,11 +72,11 @@ public class ParserService {
return result; return result;
} }
protected String parseBackSpaces(final String pSource) { String parseBackSpaces(final String pSource) {
return pSource.replaceAll("\r?\n", "<br/>").replaceAll("\\[\\/code\\]<br\\/><br\\/>", "[/code]\n"); return pSource.replaceAll("\r?\n", "<br/>").replaceAll("\\[\\/code\\]<br\\/><br\\/>", "[/code]\n");
} }
protected String parseImages(final String pSource) { String parseImages(final String pSource) {
String result = pSource; String result = pSource;
Matcher matcher = PATTERN_IMAGES.matcher(result); Matcher matcher = PATTERN_IMAGES.matcher(result);
@@ -96,7 +95,7 @@ public class ParserService {
return result; return result;
} }
protected String parseLinks(final String pSource) { String parseLinks(final String pSource) {
String result = pSource; String result = pSource;
Matcher matcher = PATTERN_LINKS.matcher(result); Matcher matcher = PATTERN_LINKS.matcher(result);

View File

@@ -17,15 +17,18 @@ import javax.swing.text.html.Option;
public class UserService { public class UserService {
private static final String MSG_BAD_CREDENTIALS = "Adresse email ou mot de passe incorrect."; private static final String MSG_BAD_CREDENTIALS = "Adresse email ou mot de passe incorrect.";
@Autowired private final UserRepository userRepository;
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User checkCredentials(final String email, final String password) throws BadCredentialsException { public User checkCredentials(final String email, final String password) throws BadCredentialsException {
final Optional<User> optUser = userRepository.findByEmail(email); final Optional<User> optUser = userRepository.findByEmail(email);
// If no user exists with the given email of if the given password doesn't match // If no user exists with the given email of if the given password doesn't match
// to the user password, we throw an exception. // to the user password, we throw an exception.
if(!optUser.isPresent() || !StringUtils.compareHash(password, optUser.get().getPassword())) { if(optUser.isEmpty() || !StringUtils.compareHash(password, optUser.get().getPassword())) {
throw new BadCredentialsException(MSG_BAD_CREDENTIALS); throw new BadCredentialsException(MSG_BAD_CREDENTIALS);
} }

View File

@@ -7,7 +7,7 @@ import java.util.Date;
import java.util.Locale; import java.util.Locale;
public class DateUtils { public class DateUtils {
public static final String FORMAT_DEFAULT = "dd/MM/yyyy HH:mm:ss"; private static final String FORMAT_DEFAULT = "dd/MM/yyyy HH:mm:ss";
public static Date parseDate(String pSource, String pPattern) throws ParseException { public static Date parseDate(String pSource, String pPattern) throws ParseException {
Date result = null; Date result = null;
@@ -27,7 +27,7 @@ public class DateUtils {
return getSimpleDateFormat(FORMAT_DEFAULT).format(pDate); return getSimpleDateFormat(FORMAT_DEFAULT).format(pDate);
} }
public static SimpleDateFormat getSimpleDateFormat(String pPattern) { private static SimpleDateFormat getSimpleDateFormat(String pPattern) {
SimpleDateFormat result = new SimpleDateFormat(pPattern, Locale.FRENCH); SimpleDateFormat result = new SimpleDateFormat(pPattern, Locale.FRENCH);
result.setLenient(false); result.setLenient(false);
return result; return result;

View File

@@ -52,9 +52,7 @@ public class ImageController {
} }
@PostMapping @PostMapping
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile pFile, public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile pFile, final Principal pPrincipal) {
final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
ResponseEntity<String> result; ResponseEntity<String> result;
try { try {
result = ResponseEntity.status(HttpStatus.OK) result = ResponseEntity.status(HttpStatus.OK)

View File

@@ -37,7 +37,7 @@ public class ImageService {
* @param fileUploadService File upload service. * @param fileUploadService File upload service.
* @param imageRepository Image repository. * @param imageRepository Image repository.
*/ */
public ImageService(UserService userService, ImageService(UserService userService,
UserRepository userRepository, UserRepository userRepository,
FileUploadService fileUploadService, FileUploadService fileUploadService,
ImageRepository imageRepository) { ImageRepository imageRepository) {
@@ -86,11 +86,11 @@ public class ImageService {
return fileUploadService.loadAvatar(pAvatarFileName); return fileUploadService.loadAvatar(pAvatarFileName);
} }
public Optional<Resource> loadImage(final String pImageLink) { Optional<Resource> loadImage(final String pImageLink) {
return fileUploadService.loadImage(pImageLink); return fileUploadService.loadImage(pImageLink);
} }
public List<ImageDTO> getUserImages(final Principal pPrincipal) { List<ImageDTO> getUserImages(final Principal pPrincipal) {
return imageRepository.getByUserId(userService.getUserByPrincipal(pPrincipal) return imageRepository.getByUserId(userService.getUserByPrincipal(pPrincipal)
.map(User::getId) .map(User::getId)
.orElseThrow(NoSuchElementException::new)) .orElseThrow(NoSuchElementException::new))

View File

@@ -8,7 +8,6 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.codiki.core.entities.dto.PostDTO; import org.codiki.core.entities.dto.PostDTO;
@@ -19,7 +18,6 @@ import org.codiki.core.repositories.PostRepository;
import org.codiki.core.services.ParserService; import org.codiki.core.services.ParserService;
import org.codiki.core.services.UserService; import org.codiki.core.services.UserService;
import org.codiki.core.utils.StringUtils; import org.codiki.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -35,7 +33,6 @@ import com.fasterxml.jackson.annotation.JsonView;
@RestController @RestController
@RequestMapping("/api/posts") @RequestMapping("/api/posts")
public class PostController { public class PostController {
private static final int LIMIT_POSTS_HOME = 20; private static final int LIMIT_POSTS_HOME = 20;
/** Service to parse post content. */ /** Service to parse post content. */
private ParserService parserService; private ParserService parserService;
@@ -112,14 +109,11 @@ public class PostController {
@JsonView(View.PostDTO.class) @JsonView(View.PostDTO.class)
@GetMapping("/myPosts") @GetMapping("/myPosts")
public List<Post> getMyPosts(final HttpServletRequest pRequest, final HttpServletResponse pResponse, public List<Post> getMyPosts(final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Principal pPrincipal) throws IOException {
List<Post> result = new LinkedList<>(); List<Post> result = new LinkedList<>();
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal); final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) { if(connectedUser.isPresent()) {
// result = postRepository.getByCreator(connectedUser.get().getId())
// .stream().map(PostDTO::new).collect(Collectors.toList());
result = postRepository.getByCreator(connectedUser.get().getId()); result = postRepository.getByCreator(connectedUser.get().getId());
} else { } else {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -142,20 +136,21 @@ public class PostController {
@JsonView(View.PostDTO.class) @JsonView(View.PostDTO.class)
@PostMapping("/") @PostMapping("/")
public Post insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest, public Post insert(@RequestBody final Post pPost,
final HttpServletResponse pResponse, final Principal pPrincipal) { final HttpServletResponse pResponse,
return postService.insert(pPost, pRequest, pResponse, pPrincipal).orElse(null); final Principal pPrincipal) {
pResponse.setStatus(postService.insert(pPost, pPrincipal));
return pPost;
} }
@PutMapping("/") @PutMapping("/")
public void update(@RequestBody final Post pPost, final HttpServletRequest pRequest, public void update(@RequestBody final Post pPost,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
postService.update(pPost, pRequest, pResponse, pPrincipal); postService.update(pPost, pResponse, pPrincipal);
} }
@DeleteMapping("/{postKey}") @DeleteMapping("/{postKey}")
public void delete(@PathVariable("postKey") final String pPostKey, final HttpServletRequest pRequest, public void delete(@PathVariable("postKey") final String pPostKey, final Principal pPrincipal) {
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { postService.delete(pPostKey, pPrincipal);
postService.delete(pPostKey, pRequest, pResponse, pPrincipal);
} }
} }

View File

@@ -1,72 +1,59 @@
package org.codiki.posts; package org.codiki.posts;
import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.codiki.core.entities.business.SearchEntity; import org.codiki.core.entities.business.SearchEntity;
import org.codiki.core.entities.dto.PostDTO;
import org.codiki.core.entities.persistence.Category; import org.codiki.core.entities.persistence.Category;
import org.codiki.core.entities.persistence.Post; import org.codiki.core.entities.persistence.Post;
import org.codiki.core.entities.persistence.User; import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.CategoryRepository; import org.codiki.core.repositories.CategoryRepository;
import org.codiki.core.repositories.PostRepository; import org.codiki.core.repositories.PostRepository;
import org.codiki.core.services.IllegalAccessException;
import org.codiki.core.services.UserService; import org.codiki.core.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.*;
import java.util.stream.Collectors;
@Service @Service
public class PostService { public class PostService {
static final int SCORE_TITLE = 50; private static final int SCORE_TITLE = 50;
private static final int SCORE_DESCRIPTION = 5;
private static final int SCORE_TEXT = 1;
private static final int SCORE_CATEGORY = 30;
private static final int SCORE_AUTHOR_NAME = 40;
private static final int SCORE_AUTHOR_EMAIL = 40;
static final int SCORE_DESCRIPTION = 5; private final PostRepository postRepository;
static final int SCORE_TEXT = 1; private final CategoryRepository categoryRepository;
static final int SCORE_CATEGORY = 30; private final UserService userService;
static final int SCORE_AUTHOR_NAME = 40; /** Constructor. */
public PostService(PostRepository postRepository,
static final int SCORE_AUTHOR_EMAIL = 40; CategoryRepository categoryRepository,
UserService userService) {
@Autowired this.postRepository = postRepository;
private PostRepository postRepository; this.categoryRepository = categoryRepository;
this.userService = userService;
@Autowired
private CategoryRepository categoryRepository;
@Autowired
private UserService userService;
public Optional<Post> insert(final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) {
Optional<Post> result = Optional.empty();
final Optional<User> user = userService.getUserByPrincipal(pPrincipal);
if(user.isPresent()) {
final Post postToSave = new Post(pPost);
postToSave.setAuthor(user.get());
postRepository.save(postToSave);
result = Optional.of(postToSave);
} else {
pResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return result;
} }
public void update(final Post pPost, final HttpServletRequest pRequest, public int insert(final Post pPost, final Principal pPrincipal) {
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { return userService.getUserByPrincipal(pPrincipal)
.map(user -> {
pPost.setCreationDate(new Date());
pPost.setAuthor(user);
postRepository.save(pPost);
return HttpServletResponse.SC_OK;
})
.orElse(HttpServletResponse.SC_UNAUTHORIZED);
}
public void update(final Post pPost, final HttpServletResponse pResponse, final Principal pPrincipal)
throws IOException {
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal); final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent() && connectedUser.get().getKey().equals(pPost.getAuthor().getKey())) { if(connectedUser.isPresent() && connectedUser.get().getKey().equals(pPost.getAuthor().getKey())) {
@@ -96,23 +83,19 @@ public class PostService {
} }
} }
public void delete(final String pPostKey, final HttpServletRequest pRequest, public void delete(final String pPostKey, final Principal pPrincipal){
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { String userKey = userService.getUserByPrincipal(pPrincipal)
final Optional<Post> postToDelete = postRepository.getByKey(pPostKey); .map(User::getKey)
if(postToDelete.isPresent()) { .orElseThrow(NoSuchElementException::new);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) { Post postToDelete = postRepository.getByKey(pPostKey)
if(connectedUser.get().getKey().equals(postToDelete.get().getAuthor().getKey())) { .orElseThrow(NoSuchElementException::new);
postRepository.delete(postToDelete.get());
} else { if(postToDelete.getAuthor() == null || !postToDelete.getAuthor().getKey().equals(userKey)) {
pResponse.sendError(HttpServletResponse.SC_FORBIDDEN); throw new IllegalAccessException();
}
} else {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
} else {
pResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
} }
postRepository.delete(postToDelete);
} }
public List<Post> search(String pSearchCriteria) { public List<Post> search(String pSearchCriteria) {
@@ -123,12 +106,12 @@ public class PostService {
calculateScore(e, criteriasArray); calculateScore(e, criteriasArray);
listSearchEntities.add(e); listSearchEntities.add(e);
}); });
Collections.sort(listSearchEntities, (e1, e2) -> e2.getScore() - e1.getScore()); listSearchEntities.sort((e1, e2) -> e2.getScore() - e1.getScore());
return listSearchEntities.stream().map(SearchEntity::getPost).collect(Collectors.toList()); return listSearchEntities.stream().map(SearchEntity::getPost).collect(Collectors.toList());
} }
void calculateScore(final SearchEntity searchPost, final String[] pCriteriasArray) { private void calculateScore(final SearchEntity searchPost, final String[] pCriteriasArray) {
for(final String criteria : pCriteriasArray) { for(final String criteria : pCriteriasArray) {
String formattedCriteria = formatForComp(criteria); String formattedCriteria = formatForComp(criteria);
@@ -141,41 +124,41 @@ public class PostService {
} }
} }
String formatForComp(final String pElem) { private String formatForComp(final String pElem) {
return StringUtils.stripAccents(pElem.toLowerCase()); return StringUtils.stripAccents(pElem.toLowerCase());
} }
void calculateScoreForTitle(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForTitle(final SearchEntity pSearchPost, final String pCriteria) {
if(formatForComp(pSearchPost.getPost().getTitle()).contains(pCriteria)) { if(formatForComp(pSearchPost.getPost().getTitle()).contains(pCriteria)) {
pSearchPost.increaseScore(SCORE_TITLE); pSearchPost.increaseScore(SCORE_TITLE);
} }
} }
void calculateScoreForDescription(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForDescription(final SearchEntity pSearchPost, final String pCriteria) {
final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getDescription()), final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getDescription()),
pCriteria); pCriteria);
pSearchPost.increaseScore(criteriaOccurence * SCORE_DESCRIPTION); pSearchPost.increaseScore(criteriaOccurence * SCORE_DESCRIPTION);
} }
void calculateScoreForText(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForText(final SearchEntity pSearchPost, final String pCriteria) {
final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getText()), final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getText()),
pCriteria); pCriteria);
pSearchPost.increaseScore(criteriaOccurence * SCORE_TEXT); pSearchPost.increaseScore(criteriaOccurence * SCORE_TEXT);
} }
void calculateScoreForCategory(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForCategory(final SearchEntity pSearchPost, final String pCriteria) {
if(formatForComp(pSearchPost.getPost().getCategory().getName()).contains(pCriteria)) { if(formatForComp(pSearchPost.getPost().getCategory().getName()).contains(pCriteria)) {
pSearchPost.increaseScore(SCORE_CATEGORY); pSearchPost.increaseScore(SCORE_CATEGORY);
} }
} }
void calculateScoreForAuthorName(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForAuthorName(final SearchEntity pSearchPost, final String pCriteria) {
if(formatForComp(pSearchPost.getPost().getAuthor().getName()).contains(pCriteria)) { if(formatForComp(pSearchPost.getPost().getAuthor().getName()).contains(pCriteria)) {
pSearchPost.increaseScore(SCORE_AUTHOR_NAME); pSearchPost.increaseScore(SCORE_AUTHOR_NAME);
} }
} }
void calculateScoreForAuthorEmail(final SearchEntity pSearchPost, final String pCriteria) { private void calculateScoreForAuthorEmail(final SearchEntity pSearchPost, final String pCriteria) {
if(formatForComp(pSearchPost.getPost().getAuthor().getEmail()).contains(pCriteria)) { if(formatForComp(pSearchPost.getPost().getAuthor().getEmail()).contains(pCriteria)) {
pSearchPost.increaseScore(SCORE_AUTHOR_EMAIL); pSearchPost.increaseScore(SCORE_AUTHOR_EMAIL);
} }

View File

@@ -13,12 +13,14 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/versionrevisions") @RequestMapping("/api/versionrevisions")
public class VersionRevisionController { public class VersionRevisionController {
private final VersionRevisionRepository repository;
private final VersionRepository versionRepository;
@Autowired public VersionRevisionController(VersionRevisionRepository repository,
private VersionRevisionRepository repository; VersionRepository versionRepository) {
this.repository = repository;
@Autowired this.versionRepository = versionRepository;
private VersionRepository versionRepository; }
@GetMapping("/versions") @GetMapping("/versions")
public Iterable<Version> getVersions() { public Iterable<Version> getVersions() {

View File

@@ -40,8 +40,7 @@ spring:
url: jdbc:postgresql://localhost:5432/codiki url: jdbc:postgresql://localhost:5432/codiki
username: codiki username: codiki
password: P@ssword password: P@ssword
# TODO: Delete all Lazy relationships and set following property to false open-in-view: false
# open-in-view: false
# Because detection is disabled you have to set correct dialect by hand. # Because detection is disabled you have to set correct dialect by hand.
database-platform: org.hibernate.dialect.PostgreSQL9Dialect database-platform: org.hibernate.dialect.PostgreSQL9Dialect
# Disable feature detection by this undocumented parameter. # Disable feature detection by this undocumented parameter.

View File

@@ -3,4 +3,5 @@ INSERT INTO version (number) VALUES ('1.2.0');
INSERT INTO version_revision (version_id, text, bugfix) VALUES INSERT INTO version_revision (version_id, text, bugfix) VALUES
(4, 'Migration vers Angular 8.2.', FALSE), (4, 'Migration vers Angular 8.2.', FALSE),
(4, 'Migration vers Java 11.', FALSE), (4, 'Migration vers Java 11.', FALSE),
(4, 'Corrections mineures et améliorations de code.', TRUE); (4, 'Corrections mineures et améliorations de code.', TRUE),
(4, 'Ajout d''un gestionnaire de notifications pop-ups.', FALSE);

View File

@@ -6,6 +6,8 @@ import { catchError } from 'rxjs/operators';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { NotificationsComponent } from '../notifications/notifications.component'; import { NotificationsComponent } from '../notifications/notifications.component';
const REG_URL_LOGIN = /^https?:\/\/.*(:\d{1,5})?\/api\/account\/login$/;
@Injectable() @Injectable()
export class UnauthorizedInterceptor implements HttpInterceptor { export class UnauthorizedInterceptor implements HttpInterceptor {
constructor( constructor(
@@ -18,7 +20,9 @@ export class UnauthorizedInterceptor implements HttpInterceptor {
if (err.status === 401) { if (err.status === 401) {
this.authService.setAnonymous(); this.authService.setAnonymous();
this.router.navigate(['/login']); this.router.navigate(['/login']);
window.setTimeout(() => NotificationsComponent.error('Veuillez vous authentifier pour réaliser cette action.'), 500); if (!err.url.match(REG_URL_LOGIN)) {
window.setTimeout(() => NotificationsComponent.error('Veuillez vous authentifier pour réaliser cette action.'), 500);
}
} }
const error = (err.error && err.error.message) || err.statusText; const error = (err.error && err.error.message) || err.statusText;

View File

@@ -17,8 +17,16 @@
</a> </a>
</li> </li>
<li class="nav-item dropdown" dropdown *ngIf="isAuthenticated()"> <li class="nav-item dropdown" dropdown *ngIf="isAuthenticated()">
<a dropdownToggle mdbRippleRadius type="button" class="nav-link dropdown-toggle waves-light" mdbRippleRadius> <a dropdownToggle
<i class="fa fa-user-circle"></i> Mon Compte mdbRippleRadius
type="button"
class="nav-link waves-light"
mdbTooltip="Mon compte"
placement="bottom"
mdbRippleRadius>
<img id="user-profile-img"
[src]="getConnectedUserImage()"
(error)="onUserProfileLoadingError($event)"/>
</a> </a>
<div *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown dropdown-primary" role="menu"> <div *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown dropdown-primary" role="menu">
<a class="dropdown-item waves-light" mdbRippleRadius routerLink="/myPosts"> <a class="dropdown-item waves-light" mdbRippleRadius routerLink="/myPosts">
@@ -47,7 +55,10 @@
<i class="fa fa-chevron-left"></i> <i class="fa fa-chevron-left"></i>
</a> </a>
<div *ngFor="let category of listCategories"> <div *ngFor="let category of listCategories">
<a [id]="'category-' + category.id" class="collapsible" *ngIf="category.listSubCategories.length" (click)="openCategoriesLinks(category)"> <a [id]="'category-' + category.id"
class="collapsible"
*ngIf="!!category.listSubCategories && category.listSubCategories.length"
(click)="openCategoriesLinks(category)">
{{category.name}} <i class="fa {{accordionOpenned[category.id] ? 'fa-chevron-down' : 'fa-chevron-right'}} float-right"></i> {{category.name}} <i class="fa {{accordionOpenned[category.id] ? 'fa-chevron-down' : 'fa-chevron-right'}} float-right"></i>
</a> </a>
<div class="categoriesLinks" > <div class="categoriesLinks" >

View File

@@ -27,42 +27,48 @@
overflow-x: hidden; /* Disable horizontal scroll */ overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 20px; /* Place content 60px from the top */ padding-top: 20px; /* Place content 60px from the top */
transition: 0.3s; /* 0.5 second transition effect to slide in the sidenav */ transition: 0.3s; /* 0.5 second transition effect to slide in the sidenav */
}
/* The navigation menu links */ /* The navigation menu links */
.sidenav a { a {
padding: 8px 32px 8px 32px; padding: 8px 32px 8px 32px;
text-decoration: none; text-decoration: none;
color: white; color: white;
display: block; display: block;
transition: 0.3s; transition: 0.3s;
}
/* When you mouse over the navigation links, change their color */ /* When you mouse over the navigation links, change their color */
.sidenav a:hover { &:hover {
color: #ccc; color: #ccc;
background-color: #5c6bc0; background-color: #5c6bc0;
} }
}
.sidenav h3 { h3 {
padding: 8px 8px 8px 32px; padding: 8px 8px 8px 32px;
color: white; color: white;
padding-bottom: 25px; padding-bottom: 25px;
border-bottom: 1px solid #5c6bc0; border-bottom: 1px solid #5c6bc0;
} }
/* Position and style the close button (top right corner) */
.sidenav .closebtn { /* Position and style the close button (top right corner) */
position: absolute; .closebtn {
top: 25px; position: absolute;
right: 25px; top: 25px;
margin-left: 50px; right: 25px;
padding: 8px; margin-left: 50px;
padding: 8px;
}
} }
/* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */ /* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
@media screen and (max-height: 450px) { @media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;} .sidenav {
.sidenav a {font-size: 18px;} padding-top: 15px;
a {
font-size: 18px;
}
}
} }
#overlay { #overlay {
@@ -74,8 +80,8 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0,0,0,0.5); /* Black background with opacity */ background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */
z-index: 999; /* Specify a stack order in case you're using a different order for other elements */ z-index: 1031; /* Specify a stack order in case you're using a different order for other elements */
transition: 0.5s; transition: 0.5s;
} }
@@ -84,6 +90,10 @@
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
transition: max-height 0.2s ease-out; transition: max-height 0.2s ease-out;
a {
padding-left: 50px;
}
} }
.dropdown-item.danger-color-dark:hover { .dropdown-item.danger-color-dark:hover {
@@ -93,3 +103,15 @@
.collapsible i.fa { .collapsible i.fa {
padding-top: 4px; padding-top: 4px;
} }
#user-profile-img {
filter: opacity(1);
transition: filter 0.3s;
border-radius: 50%;
width: 35px;
height: 35px;
&:hover {
filter: opacity(0.5);
}
}

View File

@@ -6,6 +6,7 @@ import { HeaderService } from './header.service';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
const SIDENAV_WIDTH = '300px'; const SIDENAV_WIDTH = '300px';
const DEFAULT_USER_ICON = '../../assets/images/default_user_icon.png';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
@@ -80,4 +81,13 @@ export class HeaderComponent implements OnInit {
this.accordionOpenned[category.id] = true; this.accordionOpenned[category.id] = true;
} }
} }
getConnectedUserImage() {
const userImage = this.authService.getUser().image;
return !!userImage ? `./api/images/loadAvatar/${userImage}` : DEFAULT_USER_ICON;
}
onUserProfileLoadingError(event): void {
event.target.src = DEFAULT_USER_ICON;
}
} }

View File

@@ -67,7 +67,7 @@ export class CreateUpdatePostComponent implements OnInit {
this.listCategories = []; this.listCategories = [];
this.activatedTab = Tabs.EDITION; this.activatedTab = Tabs.EDITION;
this.createUpdatePostService.getCategories().subscribe(listCategories => { this.createUpdatePostService.getCategories().subscribe(listCategories => {
this.listCategories = listCategories.filter(category => !category.listSubCategories.length); this.listCategories = listCategories.filter(category => !category.listSubCategories);
}); });
const postKey = this.activatedRoute.snapshot.paramMap.get('postKey'); const postKey = this.activatedRoute.snapshot.paramMap.get('postKey');
@@ -166,6 +166,7 @@ export class CreateUpdatePostComponent implements OnInit {
}); });
} else { } else {
this.createUpdatePostService.addPost(this.model).subscribe(post => { this.createUpdatePostService.addPost(this.model).subscribe(post => {
NotificationsComponent.success('Article enregistré');
this.router.navigate([`/posts/update/${post.key}`]); this.router.navigate([`/posts/update/${post.key}`]);
}, error => { }, error => {
console.log(error); console.log(error);

View File

@@ -7,5 +7,9 @@
<span *ngIf="listPosts?.length === 0"> <span *ngIf="listPosts?.length === 0">
Aucun article. Aucun article.
</span> </span>
<a routerLink="/posts/new" class="fixed-action-btn green white-text">+</a> <a routerLink="/posts/new"
class="fixed-action-btn green white-text"
mdbTooltip="Créer un article" placement="left">
+
</a>
</div> </div>

View File

@@ -43,9 +43,6 @@
<div class="modal-body"> <div class="modal-body">
<div class="text-center"> <div class="text-center">
<p>Êtes vous sûr de vouloir supprimer cet article ?</p> <p>Êtes vous sûr de vouloir supprimer cet article ?</p>
<p *ngIf="postDeletionFailed" class="red-text">
Une erreur est survenue lors de la suppression de l'article.
</p>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@@ -1,3 +1,4 @@
import { NotificationsComponent } from 'src/app/core/notifications/notifications.component';
import { Component, OnInit, SecurityContext, ViewChild } from '@angular/core'; import { Component, OnInit, SecurityContext, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
@@ -38,9 +39,7 @@ export class PostComponent implements OnInit {
notFound: boolean; notFound: boolean;
owned: boolean; owned: boolean;
@ViewChild('alertDelete', {static: true}) alertDelete; @ViewChild('alertDelete', {static: false}) alertDelete;
postDeletionFailed: boolean;
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
@@ -51,7 +50,6 @@ export class PostComponent implements OnInit {
) { ) {
this.loaded = false; this.loaded = false;
this.owned = false; this.owned = false;
this.postDeletionFailed = false;
} }
ngOnInit(): void { ngOnInit(): void {
@@ -91,16 +89,13 @@ export class PostComponent implements OnInit {
} }
deletePost(): void { deletePost(): void {
this.postDeletionFailed = false;
this.postService.deletePost(this.post).subscribe(() => { this.postService.deletePost(this.post).subscribe(() => {
NotificationsComponent.success('Article supprimé.');
this.alertDelete.hide(); this.alertDelete.hide();
this.router.navigate(['/myPosts']); this.router.navigate(['/myPosts']);
}, error => { }, error => {
this.postDeletionFailed = true; console.error(error);
setTimeout(() => { NotificationsComponent.error('Une erreur est survenue lors de la suppression de l\'article.');
this.postDeletionFailed = false;
}, 3500);
}); });
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -6,7 +6,7 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<base href="/" /> <base href="/" />
<link rel="icon" type="image/x-icon" href="assets/images/favicon.png" /> <link rel="icon" type="image/x-icon" href="assets/images/favicon.ico" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link href="./assets/css/prism.css" rel="stylesheet" /> <link href="./assets/css/prism.css" rel="stylesheet" />
</head> </head>