diff --git a/src/main/java/org/codiki/account/AccountController.java b/src/main/java/org/codiki/account/AccountController.java index efa79a6..2aee74d 100755 --- a/src/main/java/org/codiki/account/AccountController.java +++ b/src/main/java/org/codiki/account/AccountController.java @@ -10,13 +10,21 @@ import org.codiki.core.entities.dto.PasswordWrapperDTO; import org.codiki.core.entities.dto.UserDTO; import org.codiki.core.entities.persistence.User; import org.codiki.core.security.TokenService; +import org.codiki.core.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api/account") @@ -82,4 +90,24 @@ public class AccountController { pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); } } + + @PostMapping("/uploadAvatar") + public ResponseEntity uploadAvatar(@RequestParam("file") MultipartFile pFile) { + String result; + try { + result = accountService.uploadFile(pFile); + return ResponseEntity.status(HttpStatus.OK).body(result); + } catch(final Exception pEx) { + result = StringUtils.concat("Fail to upload ", pFile.getOriginalFilename() + "."); + return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(result); + } + } + + @GetMapping("/loadAvatar/{avatarFileName}") + public ResponseEntity loadAvatar(@PathVariable("avatarFileName") final String pAvatarFileName) { + final Resource avatarFile = accountService.loadAvatar(pAvatarFileName); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, StringUtils.concat("attachment; filename=\"", avatarFile.getFilename(), "\"")) + .body(avatarFile); + } } diff --git a/src/main/java/org/codiki/account/AccountService.java b/src/main/java/org/codiki/account/AccountService.java index 32e568e..a6d20c9 100755 --- a/src/main/java/org/codiki/account/AccountService.java +++ b/src/main/java/org/codiki/account/AccountService.java @@ -11,19 +11,25 @@ import org.codiki.core.entities.dto.UserDTO; import org.codiki.core.entities.persistence.User; import org.codiki.core.repositories.UserRepository; import org.codiki.core.security.TokenService; +import org.codiki.core.services.FileUploadService; import org.codiki.core.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; @Service public class AccountService { - + @Autowired private UserRepository userRepository; @Autowired private TokenService tokenService; + @Autowired + private FileUploadService fileUploadService; + /** * Check the user credentials and generate him a token if they are correct. * @@ -68,4 +74,12 @@ public class AccountService { "Le mot de passe saisi ne correspond pas au votre."); } } + + public String uploadFile(final MultipartFile pFile) { + return fileUploadService.uploadProfileImage(pFile); + } + + public Resource loadAvatar(final String pAvatarFileName) { + return fileUploadService.loadAvatar(pAvatarFileName); + } } diff --git a/src/main/java/org/codiki/core/constant/FileEnum.java b/src/main/java/org/codiki/core/constant/FileEnum.java new file mode 100755 index 0000000..9b37e83 --- /dev/null +++ b/src/main/java/org/codiki/core/constant/FileEnum.java @@ -0,0 +1,20 @@ +package org.codiki.core.constant; + +public enum FileEnum { + /** Folder in where pictures will be uploaded. */ + FOLDER_UPLOAD("/opt/codiki/pictures/tmp/"), + /** Folder in where profile pictures will be stored. */ + FOLDER_PROFILE_IMAGES("/opt/codiki/pictures/profiles/"), + /** Folder in where images will be stored. */ + FOLDER_IMAGE("/opt/codiki/pictures/posts/"); + + private String value; + + private FileEnum(final String pValue) { + value = pValue; + } + + public String val() { + return value; + } +} diff --git a/src/main/java/org/codiki/core/security/CorsFilter.java b/src/main/java/org/codiki/core/security/CorsFilter.java new file mode 100755 index 0000000..091a1ee --- /dev/null +++ b/src/main/java/org/codiki/core/security/CorsFilter.java @@ -0,0 +1,44 @@ +package org.codiki.core.security; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CorsFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Do nothing + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.addHeader("Access-Control-Allow-Origin", "*"); + httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD"); + httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"); + httpResponse.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials"); + httpResponse.addHeader("Access-Control-Allow-Credentials", "true"); + httpResponse.addIntHeader("Access-Control-Max-Age", 10); + chain.doFilter(request, httpResponse); + } + + @Override + public void destroy() { + // Do nothing + } + +} diff --git a/src/main/java/org/codiki/core/services/FileUploadService.java b/src/main/java/org/codiki/core/services/FileUploadService.java new file mode 100755 index 0000000..16f8a8f --- /dev/null +++ b/src/main/java/org/codiki/core/services/FileUploadService.java @@ -0,0 +1,61 @@ +package org.codiki.core.services; + +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.commons.lang.RandomStringUtils; +import org.codiki.core.constant.FileEnum; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class FileUploadService { + /** Length of uploaded file names. */ + private static final int DESTINATION_IMAGE_NAME_LENGTH = 30; + + /** + * Builds the destination file name, which is a random string with 30 char + * length. + * + * @return The file name. + */ + private String buildDestinationFileName() { + return RandomStringUtils.randomAlphanumeric(DESTINATION_IMAGE_NAME_LENGTH); + } + + public String uploadProfileImage(final MultipartFile pFile) { + return uploadFile(pFile, FileEnum.FOLDER_PROFILE_IMAGES); + } + + private String uploadFile(final MultipartFile pFile, final FileEnum pPath) { + String result; + try { + result = buildDestinationFileName(); + Files.copy(pFile.getInputStream(), Paths.get(pPath.val()).resolve(result)); + return result; + } catch (final Exception pEx) { + // TODO : Refactor exception + throw new RuntimeException(); + } + } + + public Resource loadAvatar(final String pAvatarFileName) { + try { + Path avatarFile = Paths.get(FileEnum.FOLDER_PROFILE_IMAGES.val()).resolve(pAvatarFileName); + Resource avatarResource = new UrlResource(avatarFile.toUri()); + if(avatarResource.exists() || avatarResource.isReadable()) { + return avatarResource; + } else { + // TODO : Refactor exception + throw new RuntimeException(); + } + } catch(final MalformedURLException pEx) { + // TODO : Refactor exception + throw new RuntimeException(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8d24795..268cc4b 100755 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,4 +12,6 @@ spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect logging.level.org.hibernate=DEBUG +spring.servlet.multipart.max-file-size=104857600 + cors.enabled=false \ No newline at end of file