From 27a830d2f4ff3dfe83b375fca4f4b519051cac5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakob=20Gr=C3=B8nhaug?= <jakob@gronha.ug>
Date: Tue, 2 May 2023 10:28:20 +0200
Subject: [PATCH] =?UTF-8?q?Implementer=20filh=C3=A5ndtering=20for=20profil?=
 =?UTF-8?q?bilder?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/FileController.java            | 154 ++++++++++++++++++
 .../security/SecurityConfig.java              |   7 +-
 src/main/resources/application.properties     |   2 +
 3 files changed, 160 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/edu/ntnu/idatt210602/matsvinnbackend/controller/FileController.java

diff --git a/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/controller/FileController.java b/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/controller/FileController.java
new file mode 100644
index 0000000..382f092
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/controller/FileController.java
@@ -0,0 +1,154 @@
+package edu.ntnu.idatt210602.matsvinnbackend.controller;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.DeleteMapping;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import edu.ntnu.idatt210602.matsvinnbackend.model.Account;
+import edu.ntnu.idatt210602.matsvinnbackend.model.Profile;
+import edu.ntnu.idatt210602.matsvinnbackend.repo.AccountRepository;
+import edu.ntnu.idatt210602.matsvinnbackend.repo.ProfileRepository;
+
+import java.nio.file.Path;
+
+@Controller
+@RequestMapping(path = "/img")
+public class FileController {
+
+    Logger logger = LoggerFactory.getLogger(ProfileController.class);
+
+    @Value("${filebucket.path}")
+    String basePath;
+
+    @Autowired
+    AccountRepository accountRepo;
+
+    @Autowired
+    ProfileRepository profileRepo;
+
+    @PostMapping("")
+    public ResponseEntity<String> uploadProfilePicture(@RequestParam("file") MultipartFile file,
+            @RequestParam("profileId") Integer profileId) {
+        String authenticatedUsername = SecurityContextHolder.getContext().getAuthentication().getName();
+        Account loggedInAccount = accountRepo.findByEmail(authenticatedUsername).orElseThrow();
+
+        // Ensure that the provided profile ID is valid
+        Profile profile = profileRepo.findById(profileId).orElseThrow(() -> {
+            return new ResponseStatusException(HttpStatus.BAD_REQUEST);
+        });
+
+        // Ensure that the profile is part of the authenticated account
+        if (!loggedInAccount.getId().equals(profile.getAccountId())) {
+            throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+        }
+
+        // Allow only JPEG images
+        if (!file.getContentType().equals(MediaType.IMAGE_JPEG_VALUE)) {
+            throw new ResponseStatusException(HttpStatus.UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        // Only allow images up to 512 kilobytes
+        if (file.getSize() > 524288) {
+            throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE);
+        }
+
+        // Use unique profile ID as filename for 1:1 mapping between profiles and images
+        String filename = String.format("%d.jpeg", profileId);
+        Path path = Paths.get(basePath, filename);
+
+        try {
+            Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
+        } catch (IOException e) {
+            logger.error("Unable to write uploaded file to storage!");
+        }
+
+        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
+                .path("/img/")
+                .path(profileId.toString())
+                .toUriString();
+
+        return ResponseEntity.ok(fileDownloadUri);
+    }
+
+    @GetMapping("/{profileId:.+}")
+    public ResponseEntity<Resource> get(@PathVariable Integer profileId) {
+        String authenticatedUsername = SecurityContextHolder.getContext().getAuthentication().getName();
+        Account loggedInAccount = accountRepo.findByEmail(authenticatedUsername).orElseThrow();
+
+        // Ensure that the provided profile ID is valid
+        Profile profile = profileRepo.findById(profileId).orElseThrow(() -> {
+            return new ResponseStatusException(HttpStatus.BAD_REQUEST);
+        });
+
+        // Ensure that the profile is part of the authenticated account
+        if (!loggedInAccount.getId().equals(profile.getAccountId())) {
+            throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+        }
+
+        Path path = Paths.get(basePath, String.format("%d.jpeg", profileId));
+
+        if (!path.toFile().exists()) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
+        }
+
+        Resource file = null;
+
+        try {
+            file = new UrlResource(path.toUri());
+        } catch (Exception e) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
+        }
+
+        return ResponseEntity.ok()
+                .contentType(MediaType.IMAGE_JPEG)
+                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
+                .body(file);
+    }
+
+    @DeleteMapping("")
+    public ResponseEntity<Void> deleteImage(@RequestParam("profileId") Integer profileId) {
+        String authenticatedUsername = SecurityContextHolder.getContext().getAuthentication().getName();
+        Account loggedInAccount = accountRepo.findByEmail(authenticatedUsername).orElseThrow();
+
+        // Ensure that the provided profile ID is valid
+        Profile profile = profileRepo.findById(profileId).orElseThrow(() -> {
+            return new ResponseStatusException(HttpStatus.BAD_REQUEST);
+        });
+
+        // Ensure that the profile is part of the authenticated account
+        if (!loggedInAccount.getId().equals(profile.getAccountId())) {
+            throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+        }
+
+        Path path = Paths.get(basePath, String.format("%d.jpeg", profileId));
+
+        if (path.toFile().delete()) {
+            return ResponseEntity.ok().build();
+        } else {
+            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/security/SecurityConfig.java b/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/security/SecurityConfig.java
index 2bc31e6..d744c41 100644
--- a/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/security/SecurityConfig.java
+++ b/src/main/java/edu/ntnu/idatt210602/matsvinnbackend/security/SecurityConfig.java
@@ -54,9 +54,10 @@ public class SecurityConfig {
 
 
                 //FILE ENDPOINTS
-                /*.requestMatchers(HttpMethod.GET, "/files").permitAll()
-                .requestMatchers(HttpMethod.GET, "/files/*").permitAll()
-                .requestMatchers(HttpMethod.POST, "/files").permitAll()*/
+                .requestMatchers(HttpMethod.GET, "/img").authenticated()
+                .requestMatchers(HttpMethod.GET, "/img/*").authenticated()
+                .requestMatchers(HttpMethod.POST, "/img").authenticated()
+                .requestMatchers(HttpMethod.DELETE, "/img/*").authenticated()
 
                 .anyRequest().authenticated().and()
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ed5777a..cc89111 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -5,3 +5,5 @@ spring.datasource.password=db
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 
 kassalapp.cache.url=http://${KASSALAPP_CACHE_HOST:localhost}:2730
+
+filebucket.path=${FILESTORAGE_PATH:/tmp}
\ No newline at end of file
-- 
GitLab