Skip to content

Commit

Permalink
feat: Setup loading images to the temp directory
Browse files Browse the repository at this point in the history
  • Loading branch information
PolinaPolupan committed Mar 4, 2025
1 parent 3a9c8e4 commit e9da52f
Show file tree
Hide file tree
Showing 18 changed files with 612 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ out/

### VS Code ###
.vscode/
/upload-image-dir/
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
id("io.spring.dependency-management") version "1.1.7"
}

group = "com.example"
group = "com.example.pixel"
version = "0.0.1-SNAPSHOT"

java {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/example/mypixel/ErrorInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.mypixel;

public class ErrorInfo {
public final String url;
public final String ex;

public ErrorInfo(String url, Exception ex) {
this.url = url;
this.ex = ex.getLocalizedMessage();
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/example/mypixel/MyPixelApplication.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package com.example.mypixel;

import com.example.mypixel.storage.StorageProperties;
import com.example.mypixel.storage.StorageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class MyPixelApplication {

public static void main(String[] args) {
SpringApplication.run(MyPixelApplication.class, args);
}

@Bean
CommandLineRunner init(StorageService storageService) {
return (args) -> {
storageService.deleteAll();
storageService.init();
};
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/example/mypixel/controller/ErrorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.example.mypixel.controller;

import com.example.mypixel.ErrorInfo;
import com.example.mypixel.exception.InvalidImageFormat;
import com.example.mypixel.exception.StorageException;
import com.example.mypixel.exception.StorageFileNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class ErrorController {

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(StorageFileNotFoundException.class)
public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException ex, HttpServletRequest request) {
String requestUrl = request.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(requestUrl, ex);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorInfo);
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(InvalidImageFormat.class)
public ResponseEntity<?> handleInvalidImageFormat(InvalidImageFormat ex, HttpServletRequest request) {
String requestUrl = request.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(requestUrl, ex);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorInfo);
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(StorageException.class)
public ResponseEntity<?> handleStorageException(StorageException ex, HttpServletRequest request) {
String requestUrl = request.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(requestUrl, ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.example.mypixel.controller;


import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

import com.example.mypixel.exception.InvalidImageFormat;
import com.example.mypixel.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
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.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@RestController
public class ImageUploadController {

private final StorageService storageService;

@Autowired
public ImageUploadController(StorageService storageService) {
this.storageService = storageService;
}

@GetMapping("/")
public List<String> listUploadedFiles() {

return storageService.loadAll().map(
path -> MvcUriComponentsBuilder.fromMethodName(ImageUploadController.class,
"serveFile", path.getFileName().toString()).build().toUri().toString())
.collect(Collectors.toList());
}

@GetMapping("/images/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> serveFile(@PathVariable String filename) throws IOException {

Resource file = storageService.loadAsResource(filename);

if (file == null)
return ResponseEntity.notFound().build();

String location = ServletUriComponentsBuilder
.fromCurrentRequest()
.toUriString();

return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.header(HttpHeaders.LOCATION, location)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
}

@PostMapping("/")
public ResponseEntity<Void> handleFileUpload(@RequestParam("file") MultipartFile file) {

String contentType = file.getContentType();
if (!contentType.equals("image/jpeg") && !contentType.equals("image/png")) {
throw new InvalidImageFormat("Only JPEG or PNG images are allowed");
}

storageService.store(file);

String filename = file.getOriginalFilename();

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/images/{filename}")
.buildAndExpand(filename)
.toUri();

return ResponseEntity.created(location).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.mypixel.exception;

public class InvalidImageFormat extends IllegalArgumentException {

public InvalidImageFormat(String message) {
super(message);
}

public InvalidImageFormat(String message, Throwable cause) {
super(message, cause);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/example/mypixel/exception/StorageException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.mypixel.exception;

public class StorageException extends RuntimeException {

public StorageException(String message) {
super(message);
}

public StorageException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.mypixel.exception;

public class StorageFileNotFoundException extends StorageException {

public StorageFileNotFoundException(String message) {
super(message);
}

public StorageFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/example/mypixel/storage/StorageProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.mypixel.storage;

import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("storage")
public class StorageProperties {

/**
* Folder location for storing images
*/
@Value("${storage.temp-images}")
private String location;

public String getLocation() {
return location;
}

public void setLocation(String location) {
this.location = location;
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/example/mypixel/storage/StorageService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.mypixel.storage;

import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Path;
import java.util.stream.Stream;

public interface StorageService {

void init();

void store(MultipartFile file);

Stream<Path> loadAll();

Path load(String filename);

Resource loadAsResource(String filename);

void deleteAll();
}
102 changes: 102 additions & 0 deletions src/main/java/com/example/mypixel/storage/TempStorageService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.example.mypixel.storage;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;

import com.example.mypixel.exception.StorageException;
import com.example.mypixel.exception.StorageFileNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;

@Service
public class TempStorageService implements StorageService {

private final Path rootLocation;

@Autowired
public TempStorageService(StorageProperties properties) {

if (properties.getLocation().trim().length() == 0) {
throw new StorageException("File upload location can not be Empty.");
}

this.rootLocation = Paths.get(properties.getLocation());
}

@Override
public void store(MultipartFile file) {
try {
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file.");
}
Path destinationFile = this.rootLocation.resolve(
Paths.get(file.getOriginalFilename()))
.normalize().toAbsolutePath();
if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) {
// This is a security check
throw new StorageException("Cannot store file outside current directory.");
}
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream, destinationFile,
StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new StorageException("Failed to store file.", e);
}
}

@Override
public Stream<Path> loadAll() {
try {
return Files.walk(this.rootLocation, 1)
.filter(path -> !path.equals(this.rootLocation))
.map(this.rootLocation::relativize);
} catch (IOException e) {
throw new StorageException("Failed to read stored files", e);
}
}

@Override
public Path load(String filename) {
return rootLocation.resolve(filename);
}

@Override
public Resource loadAsResource(String filename) {
try {
Path file = load(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new StorageFileNotFoundException("Could not read file: " + filename);
}
} catch (MalformedURLException e) {
throw new StorageFileNotFoundException("Could not read file: " + filename, e);
}
}

@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile());
}

@Override
public void init() {
try {
Files.createDirectories(rootLocation);
} catch (IOException e) {
throw new StorageException("Could not initialize storage", e);
}
}
}
8 changes: 8 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spring:
servlet:
multipart:
max-file-size: 8MB
max-request-size: 8MB

storage:
temp-images: upload-image-dir
Loading

0 comments on commit e9da52f

Please sign in to comment.