diff --git a/mugshot/build.gradle b/mugshot/build.gradle new file mode 100644 index 0000000..6515ebf --- /dev/null +++ b/mugshot/build.gradle @@ -0,0 +1,53 @@ +buildscript { + ext { + springBootVersion = '2.1.3.RELEASE' + springCloudVersion = 'Greenwich.SR1' + } + repositories { + mavenCentral() + maven { url 'https://repo.spring.io/milestone' } + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" as Object + classpath "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" as Object + } +} + +plugins { + id "io.spring.dependency-management" version "1.0.5.RELEASE" + id 'java' + id 'org.springframework.boot' version '2.1.3.RELEASE' +} + +apply plugin: 'io.spring.dependency-management' + +group = 'hive' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '11' + +repositories { + mavenCentral() + maven { url 'https://repo.spring.io/milestone' } +} + +dependencies { + implementation project(':common') + implementation project(':entity') + + implementation 'javax.xml.bind:jaxb-api:2.3.1' + implementation 'javax.activation:activation:1.1.1' + implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2' + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.h2database:h2' + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootVersion}" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} diff --git a/mugshot/mugshot_images_profiles/0/ProfileImage.jpg b/mugshot/mugshot_images_profiles/0/ProfileImage.jpg new file mode 100644 index 0000000..ccc1e8b Binary files /dev/null and b/mugshot/mugshot_images_profiles/0/ProfileImage.jpg differ diff --git a/mugshot/src/main/java/hive/mugshot/MugshotApplication.java b/mugshot/src/main/java/hive/mugshot/MugshotApplication.java new file mode 100644 index 0000000..009a2fb --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/MugshotApplication.java @@ -0,0 +1,15 @@ +package hive.mugshot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +@SpringBootApplication +@EnableEurekaClient +@EntityScan( basePackages = {"hive.entity"} ) +public class MugshotApplication { + public static void main(String[] args) { + SpringApplication.run(MugshotApplication.class, args); + } +} diff --git a/mugshot/src/main/java/hive/mugshot/controller/MugshotController.java b/mugshot/src/main/java/hive/mugshot/controller/MugshotController.java new file mode 100644 index 0000000..1d4e6f3 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/controller/MugshotController.java @@ -0,0 +1,59 @@ +package hive.mugshot.controller; + +import hive.mugshot.exception.NotAcceptedFileFormatException; +import hive.mugshot.storage.ImageStorer; +import hive.common.security.HiveHeaders; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.core.io.Resource; + +import static hive.mugshot.storage.ImageUtils.validateIfHasAnImageAsExtension; + +@RestController +@RequestMapping("/") +public class MugshotController { + + @Value("${hive.mugshot.profile-image-name}") + private String imageName; + private final ImageStorer imageStorer; + + @Autowired + public MugshotController(ImageStorer imageStorer) { + this.imageStorer = imageStorer; + } + + @ResponseStatus(code = HttpStatus.OK, reason = "Profile image successfully stored") + @PostMapping + public void sendImageProfile( + @RequestParam("image") MultipartFile insertedImage, + @RequestHeader(name = HiveHeaders.AUTHENTICATED_USER_ID) final String userId + ){ + if(!validateIfHasAnImageAsExtension(insertedImage.getOriginalFilename())) { + throw new NotAcceptedFileFormatException(); + } + imageStorer.storeImageProfile(userId,insertedImage,imageName); + } + + @GetMapping(produces = MediaType.IMAGE_JPEG_VALUE) + public ResponseEntity searchProfileImage( + @RequestHeader(name = HiveHeaders.AUTHENTICATED_USER_ID) final String userId + ){ + Resource file = imageStorer.loadImage(userId,imageName); + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file); + } + + @ResponseStatus(code = HttpStatus.NO_CONTENT, reason = "Profile image successfully deleted") + @DeleteMapping + public void deleteProfileImage( + @RequestHeader(name = HiveHeaders.AUTHENTICATED_USER_ID) final String userId + ){ + imageStorer.deleteImage(userId,imageName); + } + +} diff --git a/mugshot/src/main/java/hive/mugshot/controller/UtilsController.java b/mugshot/src/main/java/hive/mugshot/controller/UtilsController.java new file mode 100644 index 0000000..7069b35 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/controller/UtilsController.java @@ -0,0 +1,31 @@ +package hive.mugshot.controller; + +import hive.mugshot.storage.ImageStorer; +import hive.mugshot.storage.ImageUtils; +import hive.common.security.HiveHeaders; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/utils") +public class UtilsController { + + private final ImageStorer imageStorer; + @Value("${hive.mugshot.profile-image-name}") + private String imageName; + + @Autowired + public UtilsController(ImageStorer imageStorer){ + this.imageStorer=imageStorer; + } + + @ResponseStatus(code = HttpStatus.OK, reason = "Random image generated and successfully stored") + @PostMapping("/generateRandomImage") + public void generateRandomImage(@RequestHeader(name = HiveHeaders.AUTHENTICATED_USER_ID) final String userId) { + var generatedImage=ImageUtils.generateRandomImage(); + imageStorer.storeImageProfile(userId,generatedImage,imageName); + } + +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/FileSizeException.java b/mugshot/src/main/java/hive/mugshot/exception/FileSizeException.java new file mode 100644 index 0000000..9d6d0e2 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/FileSizeException.java @@ -0,0 +1,7 @@ +package hive.mugshot.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.PAYLOAD_TOO_LARGE,reason = "Invalid size of file") +public class FileSizeException extends RuntimeException{ +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/ImageAlreadyExistException.java b/mugshot/src/main/java/hive/mugshot/exception/ImageAlreadyExistException.java new file mode 100644 index 0000000..c7eacc6 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/ImageAlreadyExistException.java @@ -0,0 +1,7 @@ +package hive.mugshot.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "The Image already exists") +public class ImageAlreadyExistException extends RuntimeException{ +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/ImageNotFound.java b/mugshot/src/main/java/hive/mugshot/exception/ImageNotFound.java new file mode 100644 index 0000000..5fbfa6f --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/ImageNotFound.java @@ -0,0 +1,7 @@ +package hive.mugshot.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Image not found for this user") +public class ImageNotFound extends RuntimeException{ +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/ImageProfileException.java b/mugshot/src/main/java/hive/mugshot/exception/ImageProfileException.java new file mode 100644 index 0000000..2137cef --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/ImageProfileException.java @@ -0,0 +1,7 @@ +package hive.mugshot.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Failed to store the image due to some I/O problem or permission") +public class ImageProfileException extends RuntimeException{ +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/InvalidPathException.java b/mugshot/src/main/java/hive/mugshot/exception/InvalidPathException.java new file mode 100644 index 0000000..47a2a5e --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/InvalidPathException.java @@ -0,0 +1,8 @@ +package hive.mugshot.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid path") +public class InvalidPathException extends RuntimeException { +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/NotAcceptedFileFormatException.java b/mugshot/src/main/java/hive/mugshot/exception/NotAcceptedFileFormatException.java new file mode 100644 index 0000000..8f58da2 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/NotAcceptedFileFormatException.java @@ -0,0 +1,7 @@ +package hive.mugshot.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.UNSUPPORTED_MEDIA_TYPE, reason = "Media type unsupported") +public class NotAcceptedFileFormatException extends RuntimeException{ +} diff --git a/mugshot/src/main/java/hive/mugshot/exception/UserNotFoundException.java b/mugshot/src/main/java/hive/mugshot/exception/UserNotFoundException.java new file mode 100644 index 0000000..948d8d0 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/exception/UserNotFoundException.java @@ -0,0 +1,8 @@ +package hive.mugshot.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "The user does not exist") +public class UserNotFoundException extends RuntimeException { +} diff --git a/mugshot/src/main/java/hive/mugshot/repository/UserRepository.java b/mugshot/src/main/java/hive/mugshot/repository/UserRepository.java new file mode 100644 index 0000000..e431394 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/repository/UserRepository.java @@ -0,0 +1,8 @@ +package hive.mugshot.repository; + +import hive.entity.user.User; +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + User findByUsername(String username); +} diff --git a/mugshot/src/main/java/hive/mugshot/storage/ImageStorer.java b/mugshot/src/main/java/hive/mugshot/storage/ImageStorer.java new file mode 100644 index 0000000..ee4d454 --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/storage/ImageStorer.java @@ -0,0 +1,83 @@ +package hive.mugshot.storage; + +import hive.mugshot.exception.*; +import hive.mugshot.exception.InvalidPathException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.*; + +@Service +public class ImageStorer { + @Value("${hive.mugshot.image-directory-path}") + private String rootDir; + @Value("${hive.mugshot.profile-image-dimension}") + private int imageSizeInPixels; + + public void storeImageProfile(String userDirectoryName, MultipartFile insertedImage, String imageStoredName){ + createDirectoryIfNotExist(userDirectoryName); + try { + var buff = ImageUtils.resizeImageToSquare(ImageIO.read(insertedImage.getInputStream()),imageSizeInPixels); + ImageIO.write(buff, "jpg", createFullPathToTheFile(userDirectoryName, imageStoredName).toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void storeImageProfile(String userDirectoryName, BufferedImage insertedImage, String imageStoredName){ + createDirectoryIfNotExist(userDirectoryName); + try { + var buff = ImageUtils.resizeImageToSquare(insertedImage,imageSizeInPixels); + ImageIO.write(buff, "jpg", createFullPathToTheFile(userDirectoryName, imageStoredName).toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public Resource loadImage(String userDirectoryName,String imageName) { + try { + var file = createFullPathToTheFile(userDirectoryName,imageName); + var resource = new UrlResource(file.toUri()); + if (resource.exists() || resource.isReadable()) { + return resource; + }else{ + throw new ImageNotFound(); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + throw new InvalidPathException(); + } + } + + public void deleteImage(String userDirectoryName, String imageName) { + var parentDir = createFullPathToTheFile(userDirectoryName,imageName); + try { + Files.deleteIfExists(parentDir); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Unable to delete the directory.\n"+e); + } + } + + private void createDirectoryIfNotExist(String userDirectoryPath){ + Path parentDir = Paths.get(rootDir,userDirectoryPath); + if (!Files.exists(parentDir)) { + try { + Files.createDirectories(parentDir); + } catch (IOException e) { + throw new RuntimeException("Unable to create the directory.\n"+e); + } + } + } + + private Path createFullPathToTheFile(String userDirectoryName, String filename) { + return Paths.get(rootDir).resolve(userDirectoryName).resolve(filename); + } + +} diff --git a/mugshot/src/main/java/hive/mugshot/storage/ImageUtils.java b/mugshot/src/main/java/hive/mugshot/storage/ImageUtils.java new file mode 100644 index 0000000..16ffabf --- /dev/null +++ b/mugshot/src/main/java/hive/mugshot/storage/ImageUtils.java @@ -0,0 +1,65 @@ +package hive.mugshot.storage; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Random; +import java.util.regex.Pattern; + +public final class ImageUtils { + private static final String IMAGE_PATTERN = "(^.+\\.(gif|png|bmp|jpeg|jpg)$)"; + + private ImageUtils(){ + } + + public static boolean validateIfHasAnImageAsExtension(final String image){ + var pattern = Pattern.compile(IMAGE_PATTERN); + var matcher = pattern.matcher(image); + return matcher.matches(); + } + + public static BufferedImage resizeImageToSquare(BufferedImage inputtedImage,int imageSizeInPixels) { + // multi-pass bilinear div 2 + var bufferedImageWithNewSize = new BufferedImage(imageSizeInPixels, imageSizeInPixels, BufferedImage.TYPE_INT_RGB); + var reSizer = bufferedImageWithNewSize.createGraphics(); + var resizingMode= + (inputtedImage.getHeight()