Compare commits

..

19 Commits

Author SHA1 Message Date
16471e9962 Big code cleaning. 2020-01-18 15:01:55 +01:00
6e7ae115e5 Add default user icon. 2020-01-18 14:31:19 +01:00
95af38bf72 Remove old error panel to use notifications. 2020-01-18 14:31:11 +01:00
5e5fbd9377 Add a control to avoid error notif at login failure. 2020-01-18 14:30:17 +01:00
84b1dd6352 Add a tooltip for post creation button. 2020-01-18 14:29:59 +01:00
1f8a11a73c Set a favicon.ico 2020-01-18 14:29:35 +01:00
7f0ff8157f Replace "my account" button by the user icon. 2020-01-18 14:29:15 +01:00
8ec806440b Add rule for resize images. 2019-08-08 22:24:55 +02:00
55f8ed986e Merge branch 'develop' 2019-08-08 22:16:45 +02:00
781eee090f Add robots.txt and anti-indexation tags. 2019-08-08 21:33:47 +02:00
e7583a59c2 [#1] Add error notification when an error attempts when post saving. 2019-08-08 20:59:17 +02:00
8205168bce Add a "JSONified" error management. 2019-08-08 20:52:31 +02:00
6bbb618f12 Refactor the post-card component html. 2019-08-08 20:25:27 +02:00
6027878622 Some front editions. 2019-08-08 20:24:53 +02:00
063d1de074 [#2] Correction of popup color. 2019-08-08 20:24:11 +02:00
2f63b798e5 Add unit tests for parser service. 2019-08-06 18:09:30 +02:00
dbfa55e0c4 Update user documentation for version 1.2.0. 2019-08-06 18:09:04 +02:00
aba59dd11f Update user documentation for version 1.2.0. 2019-08-06 17:37:40 +02:00
6bd293f08e Mettre à jour 'src/main/ts/src/environments/environment.prod.ts' 2019-08-06 13:26:44 +02:00
54 changed files with 761 additions and 347 deletions

Binary file not shown.

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

@@ -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

@@ -0,0 +1,23 @@
package org.codiki.core.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
public class RobotsTxtController {
private static final Logger LOG = LoggerFactory.getLogger(RobotsTxtController.class);
@RequestMapping(value = "/robots.txt")
public void robots(HttpServletResponse response) {
try {
response.getWriter().write("User-agent: *\nDisallow: /\n");
} catch (IOException ex) {
LOG.info("Error during robots.txt serving.", ex);
}
}
}

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)}
@@ -23,6 +21,6 @@ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override @Override
public void commence(HttpServletRequest request, HttpServletResponse response, public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException { AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} }
} }

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;
@@ -45,18 +41,13 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() http.authorizeRequests()
// Permits all
.antMatchers( .antMatchers(
"/api/account/login", "/api/account/login",
"/api/account/logout", "/api/account/logout",
"/api/account/signin" "/api/account/signin",
"/robots.txt"
).permitAll() ).permitAll()
.antMatchers(
"/api/images/uploadAvatar",
"/api/images/myImages",
"/api/posts/myPosts",
"/api/account/changePassword",
"/api/account/"
).authenticated()
.antMatchers( .antMatchers(
HttpMethod.GET, HttpMethod.GET,
"/api/categories", "/api/categories",
@@ -66,6 +57,15 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
"/api/images/**", "/api/images/**",
"/api/posts/**" "/api/posts/**"
).permitAll() ).permitAll()
.antMatchers(
"/api/images/uploadAvatar",
"/api/images/myImages",
"/api/account/changePassword",
"/api/account/",
"/api/posts/myPosts",
"/api/posts/preview",
"/api/posts/"
).authenticated()
.anyRequest().permitAll() .anyRequest().permitAll()
.and() .and()
// Allow to avoid login form at authentication failure from Angular app // Allow to avoid login form at authentication failure from Angular app

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 int insert(final Post pPost, final Principal pPrincipal) {
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 HttpServletRequest pRequest, public void update(final Post pPost, final HttpServletResponse pResponse, final Principal pPrincipal)
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException { 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

@@ -21,6 +21,9 @@ logging:
server: server:
# use-forward-headers=true # use-forward-headers=true
error:
whitelabel:
enabled: false # Disable html error responses.
port: 8080 port: 8080
# ssl: # ssl:
# key-store: /home/takiguchi/Developpement/Java/codiki/keystore.p12 # key-store: /home/takiguchi/Developpement/Java/codiki/keystore.p12
@@ -37,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

@@ -39,9 +39,9 @@
<a routerLink="/accountSettings" class="indigo-text"> <a routerLink="/accountSettings" class="indigo-text">
Annuler Annuler
</a> </a>
<button class="float-right waves-effect waves-light indigo btn" <button class="float-right waves-effect waves-light indigo white-text btn"
type="submit" [disabled]="!profilEditionForm.form.valid"> type="submit" [disabled]="!profilEditionForm.form.valid">
Suivant Enregistrer
</button> </button>
</div> </div>
</form> </form>

View File

@@ -80,7 +80,7 @@ export class ProfilEditionComponent implements OnInit {
onSubmit(): void { onSubmit(): void {
this.profilEditionService.updateUser(this.model).subscribe(() => { this.profilEditionService.updateUser(this.model).subscribe(() => {
NotificationsComponent.success('Modification enregistrée.'); NotificationsComponent.success('Modifications enregistrées.');
}, error => { }, error => {
NotificationsComponent.error('L\'adresse mail saisie n\'est pas disponible.'); NotificationsComponent.error('L\'adresse mail saisie n\'est pas disponible.');
}); });

View File

@@ -4,6 +4,9 @@ import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/c
import { Observable, throwError } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
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 {
@@ -17,6 +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']);
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

@@ -0,0 +1,27 @@
<div class="post-card hoverable" routerLink="/posts/{{post.key}}">
<mdb-card>
<!-- Picture -->
<mdb-card-img [src]="post.image" alt="Article"></mdb-card-img>
<!-- Body -->
<mdb-card-body>
<!-- Title -->
<mdb-card-title>
<h4>{{post.title}}</h4>
</mdb-card-title>
<!-- Description -->
<mdb-card-text>
{{post.description}}
</mdb-card-text>
</mdb-card-body>
<!-- Footer -->
<div class="card-footer text-muted mt-4">
<img [src]="getAvatarUrl()"
class="author-img"
[alt]="post.author.name"
[mdbTooltip]="post.author.name"
placement="bottom"/>
Article écrit par {{post.author.name}}
<span class="creation-date-area">({{post.creationDate | date:'yyyy-MM-dd HH:mm:ss'}})</span>
</div>
</mdb-card>
</div>

View File

@@ -3,35 +3,11 @@ import { Post } from '../entities';
@Component({ @Component({
selector: 'app-post-card', selector: 'app-post-card',
template: ` templateUrl: './post-card.component.html',
<div class="card hoverable">
<div class="view hm-white-slight waves-light" mdbRippleRadius>
<img id="post-image" class="img-fluid" [src]="post.image" alt="Article" />
<a routerLink="/posts/{{post.key}}">
<div class="mask"></div>
</a>
</div>
<div class="card-body">
<h4 class="card-title">{{post.title}}</h4>
<p class="card-text">{{post.description}}</p>
</div>
<div class="card-data">
<img [src]="getAvatarUrl()"
class="author-img"
[alt]="post.author.name"
[mdbTooltip]="post.author.name"
placement="bottom"/>
Article écrit par {{post.author.name}}
<span class="creation-date-area">({{post.creationDate | date:'yyyy-MM-dd HH:mm:ss'}})</span>
</div>
</div>`,
styles: [` styles: [`
div.card { .post-card {
margin-bottom: 50px; margin-bottom: 50px;
} cursor: pointer;
.card .card-data {
padding: 15px;
background-color: #f5f5f5;
} }
.creation-date-area { .creation-date-area {
color: #bdbdbd; color: #bdbdbd;

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) */
.closebtn {
position: absolute; position: absolute;
top: 25px; top: 25px;
right: 25px; right: 25px;
margin-left: 50px; margin-left: 50px;
padding: 8px; 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');
@@ -159,11 +159,18 @@ export class CreateUpdatePostComponent implements OnInit {
if (this.model.key) { if (this.model.key) {
this.createUpdatePostService.updatePost(this.model).subscribe(post => { this.createUpdatePostService.updatePost(this.model).subscribe(post => {
NotificationsComponent.error('Modification enregistrée'); NotificationsComponent.success('Modification enregistrée');
}, error => {
console.log(error);
NotificationsComponent.error('Une erreur est survenue lors de l\'enregistrement des modifications.');
}); });
} 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 => {
console.log(error);
NotificationsComponent.error('Une erreur est survenue lors de la création du post.');
}); });
} }
} else { } else {

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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
production: true, production: true,
appVersion: '1.1.0', appVersion: '1.2.0',
title: '' title: ''
}; };

View File

@@ -3,9 +3,10 @@
<head> <head>
<title>Codiki</title> <title>Codiki</title>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<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>

View File

@@ -74,3 +74,7 @@ body {
border: 1px solid rgba(0, 0, 0, 0.125) !important; border: 1px solid rgba(0, 0, 0, 0.125) !important;
} }
/* ***** End of Card style ***** */ /* ***** End of Card style ***** */
img {
max-width: 100% !important;
}

View File

@@ -0,0 +1,330 @@
package org.codiki.core.services;
import org.apache.commons.lang.StringEscapeUtils;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ParserServiceTest {
private ParserService parserService;
@Before
public void setUp() {
parserService = new ParserService();
}
/* *********************************************************** */
/* H E A D E R S P A R S I N G */
/* *********************************************************** */
@Test
public void testParseHeaders_h1() {
final String strInit = "[h1]Header[/h1]";
final String strExpected = "<h1>Header</h1>";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseHeaders_h2() {
final String strInit = "[h2]Header[/h2]";
final String strExpected = "<h2>Header</h2>";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseHeaders_h3() {
final String strInit = "[h3]Header[/h3]";
final String strExpected = "<h3>Header</h3>";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseHeader_onlyCoupleOfTags() {
final String strInit = "[h1][h1]Test[/h1]";
final String strExpected = "[h1]<h1>Test</h1>";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseHeader_multipleCouples() {
final String strInit = "[h1]Test1[/h1] [h1]Test2[/h1]";
final String strExpected = "<h1>Test1</h1> <h1>Test2</h1>";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseHeaders() {
final String strInit = "[h1]Header1[/h1]\n" +
" Some text in header1\n" +
" [h2]Header2[/h2]\n" +
" Some text in header2\n" +
" [h3]Header3[/h3]\n" +
" Some text in header3\n" +
" [h3][h3]Header3[/h3]\n" +
" Some text in header3\n" +
" [h2]Header2[/h2]\n" +
" Some text in header2\n" +
" [h3]Header3[/h3][/h3]\n" +
" Some text in header3\n" +
" [h1]Header1[/h1]\n" +
" Some text in header1";
final String strExpected = "<h1>Header1</h1>\n" +
" Some text in header1\n" +
" <h2>Header2</h2>\n" +
" Some text in header2\n" +
" <h3>Header3</h3>\n" +
" Some text in header3\n" +
" [h3]<h3>Header3</h3>\n" +
" Some text in header3\n" +
" <h2>Header2</h2>\n" +
" Some text in header2\n" +
" <h3>Header3[/h3]</h3>\n" +
" Some text in header3\n" +
" <h1>Header1</h1>\n" +
" Some text in header1";
assertThat(parserService.parseHeaders(strInit)).isEqualTo(strExpected);
}
/* *********************************************************** */
/* B A C K S P A C E S P A R S I N G */
/* *********************************************************** */
@Test
public void testParseBackSpaces() {
final String strInit = "Hello\nworld!";
final String strExpected = "Hello<br/>world!";
assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseBackSpaces_endTagCode() {
final String strInit = "[/code]\n\n";
final String strExpected = "[/code]\n";
assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseBackSpaces_endTagCode_2() {
final String strInit = "[/code]\n";
final String strExpected = "[/code]<br/>";
assertThat(parserService.parseBackSpaces(strInit)).isEqualTo(strExpected);
}
/* *********************************************************** */
/* I M A G E S P A R S I N G */
/* *********************************************************** */
@Test
public void testParseImages_simpleImg() {
final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage\" /]");
final String strExpected = "<img src=\"pathToImage\" alt=\"\" />";
assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseImages_subTagAlt() {
final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage\" alt=\"alternative\" /]");
final String strExpected = "<img src=\"pathToImage\" alt=\"alternative\" />";
assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseImages_multipleImages() {
final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage1\" /]\n" +
" [img src=\"pathToImage2\" /]");
final String strExpected = "<img src=\"pathToImage1\" alt=\"\" />\n" +
" <img src=\"pathToImage2\" alt=\"\" />";
assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseImages() {
final String strInit = StringEscapeUtils.escapeHtml("[img src=\"pathToImage1\" /]\n" +
" [img src=\"pathToImage2\" alt=\"alternativeOfImage2\" /]\n" +
" [img src=\"pathToImage3\" lgd=\"legendOfImage3\" /]\n" +
" [img src=\"pathToImage4\" alt=\"alternativeOfImage4\" lgd=\"legendOfImage4\" /]\n" +
" [img src=\"pathToImage5\" \" /]\n" +
" [img src=\"pathToImage6\" src=\"pathToImage6_2\" /]\n" +
" [img src=\"pathToImage7\" alt=\"alternativeOfImage7\" alt=\"alternativeOfImage7_2\" /]\n" +
" [img src=\"pathToImage8\" alt=\"legendOfImage8\" alt=\"legendOfImage8_2\" /]\n" +
" [img alt=\"alternativeOfImage9\" src=\"pathToImage9\" lgd=\"legendOfImage9\" /]\n" +
" [img lgd=\"legendOfImage10\" src=\"pathToImage10\" alt=\"alternativeOfImage10\" /]\n" +
" [img lgd=\"legendOfImage11\" alt=\"alternativeOfImage11\" src=\"pathToImage11\" /]");
final String strExpected = "<img src=\"pathToImage1\" alt=\"\" />\n" +
" <img src=\"pathToImage2\" alt=\"alternativeOfImage2\" />\n" +
" [img src=&quot;pathToImage3&quot; lgd=&quot;legendOfImage3&quot; /]\n" +
" [img src=&quot;pathToImage4&quot; alt=&quot;alternativeOfImage4&quot; lgd=&quot;legendOfImage4&quot; /]\n" +
" [img src=&quot;pathToImage5&quot; &quot; /]\n" +
" [img src=&quot;pathToImage6&quot; src=&quot;pathToImage6_2&quot; /]\n" +
" [img src=&quot;pathToImage7&quot; alt=&quot;alternativeOfImage7&quot; alt=&quot;alternativeOfImage7_2&quot; /]\n" +
" [img src=&quot;pathToImage8&quot; alt=&quot;legendOfImage8&quot; alt=&quot;legendOfImage8_2&quot; /]\n" +
" [img alt=&quot;alternativeOfImage9&quot; src=&quot;pathToImage9&quot; lgd=&quot;legendOfImage9&quot; /]\n" +
" [img lgd=&quot;legendOfImage10&quot; src=&quot;pathToImage10&quot; alt=&quot;alternativeOfImage10&quot; /]\n" +
" [img lgd=&quot;legendOfImage11&quot; alt=&quot;alternativeOfImage11&quot; src=&quot;pathToImage11&quot; /]";
assertThat(parserService.parseImages(strInit)).isEqualTo(strExpected);
}
/* *********************************************************** */
/* L I N K S P A R S I N G */
/* *********************************************************** */
@Test
public void testParseLinks_simpleLink() {
final String strInit = StringEscapeUtils.escapeHtml("[link href=\"pathToLink\" txt=\"textOfLink\" /]");
final String strExpected = "<a href=\"pathToLink\">textOfLink</a>";
assertThat(parserService.parseLinks(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseLinks_multipleLinks() {
final String strInit = StringEscapeUtils.escapeHtml("[link href=\"pathToLink1\" txt=\"textOfLink1\" /]\n" +
" [link href=\"pathToLink2\" txt=\"textOfLink2\" /]");
final String strExpected = "<a href=\"pathToLink1\">textOfLink1</a>\n" +
" <a href=\"pathToLink2\">textOfLink2</a>";
assertThat(parserService.parseLinks(strInit)).isEqualTo(strExpected);
}
/* *********************************************************** */
/* C O D E P A R S I N G */
/* *********************************************************** */
@Test
public void testParseCode_simpleCode() {
final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" +
"def foo():\n" +
" return \"Hello world!\"\n" +
"[/code]\n" +
"\n"));
final String strExpected = "<pre class=\"line-numbers\"><code class=\"language-python\">def foo():\n return &quot;Hello world!&quot;\n</code></pre>";
assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseCode_multipleCodes() {
final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" +
"def foo():\n" +
" return \"Hello world!\"\n" +
"[/code]\n" +
"\n" +
"[code lg=\"java\"]\n" +
"public static void main(final String... pArgs) {\n" +
" System.out.println(\"Hello world!\");\n" +
"}\n" +
"[/code]\n" +
"\n"));
final String strExpected = "<pre class=\"line-numbers\"><code class=\"language-python\">def foo():\n return &quot;Hello world!&quot;\n</code></pre><pre class=\"line-numbers\"><code class=\"language-java\">public static void main(final String... pArgs) {\n System.out.println(&quot;Hello world!&quot;);\n}\n</code></pre>";
assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseLinks_backSlashAndCodeParsing() {
final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" +
"def foo():\n" +
" return \"Hello world!\"\n" +
"[/code]\n" +
"\n"));
final String strExpected = "<pre class=\"line-numbers\"><code class=\"language-python\">def foo():\n return &quot;Hello world!&quot;\n</code></pre>";
assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected);
}
@Test
public void testParseLinks_backSlashAndCodeParsing_multiple() {
final String strInit = parserService.parseBackSpaces(StringEscapeUtils.escapeHtml("[code lg=\"python\"]\n" +
"def foo():\n" +
" return \"Hello world!\"\n" +
"[/code]\n" +
"\n" +
"[code lg=\"java\"]\n" +
"public static void main(final String... pArgs) {\n" +
" System.out.println(\"Hello world!\");\n" +
"}\n" +
"[/code]\n" +
"\n"));
final String strExpected = "<pre class=\"line-numbers\"><code class=\"language-python\">def foo():\n return &quot;Hello world!&quot;\n</code></pre><pre class=\"line-numbers\"><code class=\"language-java\">public static void main(final String... pArgs) {\n System.out.println(&quot;Hello world!&quot;);\n}\n</code></pre>";
assertThat(parserService.parseCode(strInit)).isEqualTo(strExpected);
}
@Test
public void testParse() {
final String strInit = "\n" +
"[h1]Title 1[/h1]\n" +
"[h2]Title 2[/h2]\n" +
"[h3]Title 3[/h3]\n" +
"Some text on multiple\n" +
"lines\n" +
"like\n" +
"here!\n" +
"\n" +
"[img src=\"pathToImage\" alt=\"alternativeOfImage\" /]\n" +
"\n" +
"[code lg=\"java\"]\n" +
"public static void main(final String... pArgs) {\n" +
" System.out.println(\"Hello world!\");\n" +
"}\n" +
"[/code]\n" +
"\n" +
"\n" +
"[link href=\"pathToLink1\" txt=\"textOfLink1\" /]\n" +
"\n" +
"[img src=\"pathToImage2\" alt=\"alternativeOfImage2\" lgd=\"legendOfImage2\" /]\n" +
"[link href=\"pathToLink2\" txt=\"textOfLink2\" /]\n" +
"\n" +
"[code lg=\"python\"]\n" +
"def foo():\n" +
" return \"Hello world!\"\n" +
"[/code]\n" +
"\n" +
"";
final String strExpected = "<br/>" +
"<h1>Title 1</h1><br/>" +
"<h2>Title 2</h2><br/>" +
"<h3>Title 3</h3><br/>" +
"Some text on multiple<br/>" +
"lines<br/>" +
"like<br/>" +
"here!<br/>" +
"<br/>" +
"<img src=\"pathToImage\" alt=\"alternativeOfImage\" /><br/>" +
"<br/>" +
"<pre class=\"line-numbers\"><code class=\"language-java\">" +
"public static void main(final String... pArgs) {\n" +
" System.out.println(&quot;Hello world!&quot;);\n" +
"}\n" +
"</code></pre><br/>" +
"<a href=\"pathToLink1\">textOfLink1</a><br/>" +
"<br/>" +
"[img src=&quot;pathToImage2&quot; alt=&quot;alternativeOfImage2&quot; lgd=&quot;legendOfImage2&quot; /]<br/>" +
"<a href=\"pathToLink2\">textOfLink2</a><br/>" +
"<br/>" +
"<pre class=\"line-numbers\"><code class=\"language-python\">" +
"def foo():\n" +
" return &quot;Hello world!&quot;\n" +
"</code></pre>";
assertThat(parserService.parse(strInit)).isEqualTo(strExpected);
}
}