From 6d22da8a08f6aac536f0d35467f92d0df5ac86fb Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 9 Dec 2025 16:06:20 +0530 Subject: [PATCH 1/9] image upload api created --- .../vaultWeb/controllers/UserController.java | 16 ++++++++++ .../main/java/vaultWeb/dtos/ChatImageDto.java | 32 +++++++++++++++++++ .../vaultWeb/repositories/ChatImageRepo.java | 32 +++++++++++++++++++ .../java/vaultWeb/services/UserService.java | 12 +++++++ 4 files changed, 92 insertions(+) create mode 100644 backend/src/main/java/vaultWeb/dtos/ChatImageDto.java create mode 100644 backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java diff --git a/backend/src/main/java/vaultWeb/controllers/UserController.java b/backend/src/main/java/vaultWeb/controllers/UserController.java index d67b2a5fd..38147b4e4 100644 --- a/backend/src/main/java/vaultWeb/controllers/UserController.java +++ b/backend/src/main/java/vaultWeb/controllers/UserController.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import vaultWeb.dtos.user.UserDto; import vaultWeb.dtos.user.UserResponseDto; import vaultWeb.models.User; @@ -74,4 +75,19 @@ public ResponseEntity> getAllUsers() { userService.getAllUsers().stream().map(UserResponseDto::new).toList(); return ResponseEntity.ok(users); } + + @PostMapping("/upload-image") + @Operation( + summary = "Upload Chat Image", + description = "Uploads an image to be used in chat messages.") + public ResponseEntity uploadChatImage(@RequestParam("image") MultipartFile imageBytes, @RequestParam Integer senderUserId, + @RequestParam Integer receiverUserId) throws Exception { + + // change MultipartFile to byte[] + byte[] imageByteArray = imageBytes.getBytes(); + + String imageUrl = userService.uploadChatImage(imageByteArray,senderUserId,receiverUserId); + return ResponseEntity.ok(imageUrl); + } + } diff --git a/backend/src/main/java/vaultWeb/dtos/ChatImageDto.java b/backend/src/main/java/vaultWeb/dtos/ChatImageDto.java new file mode 100644 index 000000000..ea76fec59 --- /dev/null +++ b/backend/src/main/java/vaultWeb/dtos/ChatImageDto.java @@ -0,0 +1,32 @@ +package vaultWeb.dtos; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.OffsetDateTime; + +@Entity +@Getter +@Setter +@Table(name = "chat_images") +public class ChatImageDto { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Lob + @Column(name = "image_content", nullable = false, columnDefinition = "bytea") + private byte[] imageContent; + + @Column(name = "sender_id") + private Integer senderId; + + @Column(name = "receiver_id") + private Integer receiverId; + + @NotNull + @Column(name = "createdon", nullable = false) + private OffsetDateTime createdon; +} diff --git a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java new file mode 100644 index 000000000..02b278a89 --- /dev/null +++ b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java @@ -0,0 +1,32 @@ +package vaultWeb.repositories; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import vaultWeb.dtos.ChatImageDto; + +import java.time.OffsetDateTime; +import java.util.List; + +@Repository +public interface ChatImageRepo extends CrudRepository { + + @Modifying + @Transactional + @Query( + value = "INSERT INTO chat_images (image_content, receiver_id, sender_id,createdon) " + + "VALUES (CAST(:imageData AS bytea), :receiverId, :senderId, :createdon)", + nativeQuery = true + ) + void saveImage( + @Param("imageData") byte[] imageData, + @Param("senderId") Integer senderId, + @Param("receiverId") Integer receiverId, + @Param("createdon") OffsetDateTime createdon + ); + + List createdon(OffsetDateTime createdon); +} diff --git a/backend/src/main/java/vaultWeb/services/UserService.java b/backend/src/main/java/vaultWeb/services/UserService.java index 009547c71..008256eda 100644 --- a/backend/src/main/java/vaultWeb/services/UserService.java +++ b/backend/src/main/java/vaultWeb/services/UserService.java @@ -1,11 +1,13 @@ package vaultWeb.services; +import java.time.OffsetDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import vaultWeb.exceptions.DuplicateUsernameException; import vaultWeb.models.User; +import vaultWeb.repositories.ChatImageRepo; import vaultWeb.repositories.UserRepository; /** @@ -65,4 +67,14 @@ public boolean usernameExists(String username) { public List getAllUsers() { return userRepository.findAll(); } + private final ChatImageRepo chatImageRepo; + + public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer receiverUserId){ + // Use native insert with explicit bytea cast to avoid driver/type mismatch issues + + OffsetDateTime createdon = OffsetDateTime.now(); + chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); + + return "Image uploaded successfully"; + } } From 92f952adc05fc18725e103030e462c13c299298f Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 9 Dec 2025 18:51:04 +0530 Subject: [PATCH 2/9] image upload api ref is added --- .../main/java/vaultWeb/controllers/UserController.java | 4 ++-- .../main/java/vaultWeb/repositories/ChatImageRepo.java | 8 ++++---- backend/src/main/java/vaultWeb/services/UserService.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/vaultWeb/controllers/UserController.java b/backend/src/main/java/vaultWeb/controllers/UserController.java index 38147b4e4..b20ec44c9 100644 --- a/backend/src/main/java/vaultWeb/controllers/UserController.java +++ b/backend/src/main/java/vaultWeb/controllers/UserController.java @@ -86,8 +86,8 @@ public ResponseEntity uploadChatImage(@RequestParam("image") MultipartFi // change MultipartFile to byte[] byte[] imageByteArray = imageBytes.getBytes(); - String imageUrl = userService.uploadChatImage(imageByteArray,senderUserId,receiverUserId); - return ResponseEntity.ok(imageUrl); + String imageRef = userService.uploadChatImage(imageByteArray,senderUserId,receiverUserId); + return ResponseEntity.ok(imageRef); } } diff --git a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java index 02b278a89..455c321e3 100644 --- a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java +++ b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java @@ -14,14 +14,14 @@ @Repository public interface ChatImageRepo extends CrudRepository { - @Modifying + @Transactional @Query( - value = "INSERT INTO chat_images (image_content, receiver_id, sender_id,createdon) " + - "VALUES (CAST(:imageData AS bytea), :receiverId, :senderId, :createdon)", + value = "INSERT INTO chat_images (image_content, receiver_id, sender_id, createdon) " + + "VALUES (CAST(:imageData AS bytea), :receiverId, :senderId, :createdon) RETURNING id", nativeQuery = true ) - void saveImage( + Long saveImage( @Param("imageData") byte[] imageData, @Param("senderId") Integer senderId, @Param("receiverId") Integer receiverId, diff --git a/backend/src/main/java/vaultWeb/services/UserService.java b/backend/src/main/java/vaultWeb/services/UserService.java index 008256eda..13ca122d8 100644 --- a/backend/src/main/java/vaultWeb/services/UserService.java +++ b/backend/src/main/java/vaultWeb/services/UserService.java @@ -73,8 +73,8 @@ public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer r // Use native insert with explicit bytea cast to avoid driver/type mismatch issues OffsetDateTime createdon = OffsetDateTime.now(); - chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); + Long Ref = chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); - return "Image uploaded successfully"; + return "Image uploaded successfully with ref ID: " + Ref; } } From e28c77719777de360c638b8501cf3392674bb434 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 9 Dec 2025 21:10:50 +0530 Subject: [PATCH 3/9] copilot review comment resolved --- .../vaultWeb/controllers/ChatController.java | 2 + .../controllers/ChatImageController.java | 85 +++++++++++++++++++ .../vaultWeb/controllers/UserController.java | 16 +--- .../exceptions/GlobalExceptionHandler.java | 13 +++ .../ChatImage.java} | 4 +- .../vaultWeb/repositories/ChatImageRepo.java | 7 +- .../vaultWeb/services/ChatImageService.java | 50 +++++++++++ .../java/vaultWeb/services/UserService.java | 12 --- .../src/main/resources/application.properties | 12 ++- 9 files changed, 167 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/vaultWeb/controllers/ChatImageController.java rename backend/src/main/java/vaultWeb/{dtos/ChatImageDto.java => models/ChatImage.java} (92%) create mode 100644 backend/src/main/java/vaultWeb/services/ChatImageService.java diff --git a/backend/src/main/java/vaultWeb/controllers/ChatController.java b/backend/src/main/java/vaultWeb/controllers/ChatController.java index e2ef13db4..00ffc6ba5 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatController.java @@ -5,6 +5,8 @@ import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import vaultWeb.dtos.ChatMessageDto; import vaultWeb.models.ChatMessage; import vaultWeb.services.ChatService; diff --git a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java new file mode 100644 index 000000000..72d3c39a6 --- /dev/null +++ b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java @@ -0,0 +1,85 @@ +package vaultWeb.controllers; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import vaultWeb.services.ChatImageService; +import org.springframework.beans.factory.annotation.Value; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@RestController +@RequestMapping("/api/chat") +@Tag(name = "Chat Image Controller", description = "Handles image uploads for chat messages") +@RequiredArgsConstructor +public class ChatImageController { + + private final ChatImageService chatImageService; + @Value("${vault.chatImage.maxSizeBytes:5242880}") // default 5 MB + private long maxFileSizeBytes; + + @Value("${vault.chatImage.allowedMimeTypes:image/jpeg,image/png,image/gif,image/webp}") + private String allowedMimeTypesProp; + + /** + * Handles multipart image uploads for chat messages. + * + *

Performs basic validation on the uploaded file (non-empty, size limit, allowed MIME types) + * and delegates to {@link vaultWeb.services.ChatImageService} to persist the image and validate + * sender/receiver users. + * + * @param imageFile the uploaded multipart image file (field name: {@code image}) + * @param senderUserId the ID of the sending user (must not be null) + * @param receiverUserId the ID of the receiving user (must not be null) + * @return a 200 OK response containing an implementation-specific image reference string + * @throws IllegalArgumentException if the file is empty/invalid or if IDs are null + */ + @PostMapping(value = "/upload-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation( + summary = "Upload Chat Image", + description = "Uploads an image to be used in chat messages.") + public ResponseEntity uploadChatImage( + @RequestParam("image") MultipartFile imageFile, + @RequestParam Integer senderUserId, + @RequestParam Integer receiverUserId) { + try { + if (senderUserId == null) { + throw new IllegalArgumentException("senderUserId must not be null"); + } + if (receiverUserId == null) { + throw new IllegalArgumentException("receiverUserId must not be null"); + } + validateImage(imageFile); + byte[] imageByteArray = imageFile.getBytes(); + String imageRef = + chatImageService.uploadChatImage(imageByteArray, senderUserId, receiverUserId); + return ResponseEntity.ok(imageRef); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void validateImage(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("Image file cannot be empty"); + } + if (file.getSize() > maxFileSizeBytes) { + throw new IllegalArgumentException("Image file too large"); + } + + String contentType = file.getContentType(); + Set allowed = new HashSet<>(Arrays.asList(allowedMimeTypesProp.split(","))); + if (contentType == null || !allowed.contains(contentType)) { + throw new IllegalArgumentException("Unsupported image type. Allowed: " + allowed); + } + } +} diff --git a/backend/src/main/java/vaultWeb/controllers/UserController.java b/backend/src/main/java/vaultWeb/controllers/UserController.java index b20ec44c9..3b0fd37b5 100644 --- a/backend/src/main/java/vaultWeb/controllers/UserController.java +++ b/backend/src/main/java/vaultWeb/controllers/UserController.java @@ -2,6 +2,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + +import java.io.IOException; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -76,18 +78,4 @@ public ResponseEntity> getAllUsers() { return ResponseEntity.ok(users); } - @PostMapping("/upload-image") - @Operation( - summary = "Upload Chat Image", - description = "Uploads an image to be used in chat messages.") - public ResponseEntity uploadChatImage(@RequestParam("image") MultipartFile imageBytes, @RequestParam Integer senderUserId, - @RequestParam Integer receiverUserId) throws Exception { - - // change MultipartFile to byte[] - byte[] imageByteArray = imageBytes.getBytes(); - - String imageRef = userService.uploadChatImage(imageByteArray,senderUserId,receiverUserId); - return ResponseEntity.ok(imageRef); - } - } diff --git a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java index f0e6cdbd8..b55cdca51 100644 --- a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java +++ b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java @@ -6,6 +6,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import vaultWeb.exceptions.notfound.GroupNotFoundException; import vaultWeb.exceptions.notfound.NotMemberException; import vaultWeb.exceptions.notfound.UserNotFoundException; @@ -107,6 +108,18 @@ public ResponseEntity handlePollOptionNotFound(PollOptionNotFoundExcepti return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Poll error: " + ex.getMessage()); } + /** Handles IllegalArgumentException (validation failures) and returns 400 Bad Request. */ + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Bad request: " + ex.getMessage()); + } + + /** Handles MaxUploadSizeExceededException (multipart too large) and returns 400 Bad Request. */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity handleMaxUploadSize(MaxUploadSizeExceededException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("File too large: " + ex.getMessage()); + } + /** Handles any other RuntimeException and returns 500 Internal Server Error. */ @ExceptionHandler(RuntimeException.class) public ResponseEntity handleRuntimeException(RuntimeException ex) { diff --git a/backend/src/main/java/vaultWeb/dtos/ChatImageDto.java b/backend/src/main/java/vaultWeb/models/ChatImage.java similarity index 92% rename from backend/src/main/java/vaultWeb/dtos/ChatImageDto.java rename to backend/src/main/java/vaultWeb/models/ChatImage.java index ea76fec59..4114222d3 100644 --- a/backend/src/main/java/vaultWeb/dtos/ChatImageDto.java +++ b/backend/src/main/java/vaultWeb/models/ChatImage.java @@ -1,4 +1,4 @@ -package vaultWeb.dtos; +package vaultWeb.models; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; @@ -11,7 +11,7 @@ @Getter @Setter @Table(name = "chat_images") -public class ChatImageDto { +public class ChatImage { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java index 455c321e3..a110b23cd 100644 --- a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java +++ b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java @@ -1,18 +1,17 @@ package vaultWeb.repositories; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import vaultWeb.dtos.ChatImageDto; +import vaultWeb.models.ChatImage; import java.time.OffsetDateTime; import java.util.List; @Repository -public interface ChatImageRepo extends CrudRepository { +public interface ChatImageRepo extends CrudRepository { @Transactional @@ -28,5 +27,5 @@ Long saveImage( @Param("createdon") OffsetDateTime createdon ); - List createdon(OffsetDateTime createdon); + List createdon(OffsetDateTime createdon); } diff --git a/backend/src/main/java/vaultWeb/services/ChatImageService.java b/backend/src/main/java/vaultWeb/services/ChatImageService.java new file mode 100644 index 000000000..3ef38c48d --- /dev/null +++ b/backend/src/main/java/vaultWeb/services/ChatImageService.java @@ -0,0 +1,50 @@ +package vaultWeb.services; + +import java.time.OffsetDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import vaultWeb.exceptions.notfound.UserNotFoundException; +import vaultWeb.repositories.ChatImageRepo; +import vaultWeb.repositories.UserRepository; + +@Service +@RequiredArgsConstructor +public class ChatImageService { + + private final ChatImageRepo chatImageRepo; + private final UserRepository userRepository; + + /** + * Persists a chat image payload and links it to the given sender and receiver users. + * + *

Validates that both {@code senderUserId} and {@code receiverUserId} are provided and refer + * to existing users in the system before storing the image. The image bytes are inserted using a + * native query optimized for PostgreSQL bytea. + * + * @param imageBytes raw image bytes to store (already validated for size/type upstream) + * @param senderUserId ID of the user sending the image (must not be null; must exist) + * @param receiverUserId ID of the user receiving the image (must not be null; must exist) + * @return a human-readable reference string that includes the generated image record ID + * @throws IllegalArgumentException if either user ID is null + * @throws UserNotFoundException if either the sender or receiver cannot be found + */ + public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer receiverUserId) { + if (senderUserId == null) { + throw new IllegalArgumentException("senderUserId must not be null"); + } + if (receiverUserId == null) { + throw new IllegalArgumentException("receiverUserId must not be null"); + } + + userRepository + .findById(Long.valueOf(senderUserId)) + .orElseThrow(() -> new UserNotFoundException("Sender with id " + senderUserId + " not found")); + userRepository + .findById(Long.valueOf(receiverUserId)) + .orElseThrow(() -> new UserNotFoundException("Receiver with id " + receiverUserId + " not found")); + + OffsetDateTime createdon = OffsetDateTime.now(); + Long ref = chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); + return "Image uploaded successfully with ref ID: " + ref; + } +} diff --git a/backend/src/main/java/vaultWeb/services/UserService.java b/backend/src/main/java/vaultWeb/services/UserService.java index 13ca122d8..009547c71 100644 --- a/backend/src/main/java/vaultWeb/services/UserService.java +++ b/backend/src/main/java/vaultWeb/services/UserService.java @@ -1,13 +1,11 @@ package vaultWeb.services; -import java.time.OffsetDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import vaultWeb.exceptions.DuplicateUsernameException; import vaultWeb.models.User; -import vaultWeb.repositories.ChatImageRepo; import vaultWeb.repositories.UserRepository; /** @@ -67,14 +65,4 @@ public boolean usernameExists(String username) { public List getAllUsers() { return userRepository.findAll(); } - private final ChatImageRepo chatImageRepo; - - public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer receiverUserId){ - // Use native insert with explicit bytea cast to avoid driver/type mismatch issues - - OffsetDateTime createdon = OffsetDateTime.now(); - Long Ref = chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); - - return "Image uploaded successfully with ref ID: " + Ref; - } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index d9855ec45..2c00eb5d2 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=backend server.port=8080 # PostgreSQL DB Connection -spring.datasource.username=postgres -spring.datasource.password=root +spring.datasource.username=vault_user +spring.datasource.password=vault_password spring.datasource.url=jdbc:postgresql://localhost:5432/vaultdb spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true @@ -18,3 +18,11 @@ springdoc.swagger-ui.tagsSorter=alpha encryption.master-key=aRvdZ53Fuwf7nfoT4+VeKPYi4XOhpTSh4eshUfZIgVs= # JWT Secret Key for signing tokens jwt.secret=ab9bb63c49d6d8b4029a1e6e3b1947d34be053f8ce5a0ee391e46f393014694e + +# Chat image upload validation +vault.chatImage.maxSizeBytes=5242880 +vault.chatImage.allowedMimeTypes=image/jpeg,image/png,image/gif,image/webp + +# Enforce multipart upload size limits at Spring level (keep in sync with vault.chatImage.maxSizeBytes) +spring.servlet.multipart.max-file-size=5MB +spring.servlet.multipart.max-request-size=5MB From e1396293cc1c409c80a33285d8f5b2bb4158ced0 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Tue, 9 Dec 2025 21:17:48 +0530 Subject: [PATCH 4/9] db pass restored --- backend/src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 2c00eb5d2..4fee01e62 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=backend server.port=8080 # PostgreSQL DB Connection -spring.datasource.username=vault_user -spring.datasource.password=vault_password +spring.datasource.username=postgres +spring.datasource.password=root spring.datasource.url=jdbc:postgresql://localhost:5432/vaultdb spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true From 5b2cd6f1607f3e829a7ec36d7045db699b9a07b1 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 10 Dec 2025 07:38:17 +0530 Subject: [PATCH 5/9] splotless test fixed --- .../vaultWeb/controllers/ChatController.java | 2 -- .../controllers/ChatImageController.java | 9 +++--- .../vaultWeb/controllers/UserController.java | 4 --- .../main/java/vaultWeb/models/ChatImage.java | 29 +++++++++-------- .../vaultWeb/repositories/ChatImageRepo.java | 31 +++++++++---------- .../vaultWeb/services/ChatImageService.java | 6 ++-- 6 files changed, 37 insertions(+), 44 deletions(-) diff --git a/backend/src/main/java/vaultWeb/controllers/ChatController.java b/backend/src/main/java/vaultWeb/controllers/ChatController.java index 00ffc6ba5..e2ef13db4 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatController.java @@ -5,8 +5,6 @@ import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; import vaultWeb.dtos.ChatMessageDto; import vaultWeb.models.ChatMessage; import vaultWeb.services.ChatService; diff --git a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java index 72d3c39a6..d891560ad 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java @@ -3,7 +3,11 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -12,10 +16,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import vaultWeb.services.ChatImageService; -import org.springframework.beans.factory.annotation.Value; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; @RestController @RequestMapping("/api/chat") @@ -24,6 +24,7 @@ public class ChatImageController { private final ChatImageService chatImageService; + @Value("${vault.chatImage.maxSizeBytes:5242880}") // default 5 MB private long maxFileSizeBytes; diff --git a/backend/src/main/java/vaultWeb/controllers/UserController.java b/backend/src/main/java/vaultWeb/controllers/UserController.java index 3b0fd37b5..d67b2a5fd 100644 --- a/backend/src/main/java/vaultWeb/controllers/UserController.java +++ b/backend/src/main/java/vaultWeb/controllers/UserController.java @@ -2,14 +2,11 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; - -import java.io.IOException; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import vaultWeb.dtos.user.UserDto; import vaultWeb.dtos.user.UserResponseDto; import vaultWeb.models.User; @@ -77,5 +74,4 @@ public ResponseEntity> getAllUsers() { userService.getAllUsers().stream().map(UserResponseDto::new).toList(); return ResponseEntity.ok(users); } - } diff --git a/backend/src/main/java/vaultWeb/models/ChatImage.java b/backend/src/main/java/vaultWeb/models/ChatImage.java index 4114222d3..69a24ed7b 100644 --- a/backend/src/main/java/vaultWeb/models/ChatImage.java +++ b/backend/src/main/java/vaultWeb/models/ChatImage.java @@ -2,31 +2,30 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; +import java.time.OffsetDateTime; import lombok.Getter; import lombok.Setter; -import java.time.OffsetDateTime; - @Entity @Getter @Setter @Table(name = "chat_images") public class ChatImage { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @Lob - @Column(name = "image_content", nullable = false, columnDefinition = "bytea") - private byte[] imageContent; + @Lob + @Column(name = "image_content", nullable = false, columnDefinition = "bytea") + private byte[] imageContent; - @Column(name = "sender_id") - private Integer senderId; + @Column(name = "sender_id") + private Integer senderId; - @Column(name = "receiver_id") - private Integer receiverId; + @Column(name = "receiver_id") + private Integer receiverId; - @NotNull - @Column(name = "createdon", nullable = false) - private OffsetDateTime createdon; + @NotNull + @Column(name = "createdon", nullable = false) + private OffsetDateTime createdon; } diff --git a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java index a110b23cd..8dbab1e62 100644 --- a/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java +++ b/backend/src/main/java/vaultWeb/repositories/ChatImageRepo.java @@ -1,5 +1,7 @@ package vaultWeb.repositories; +import java.time.OffsetDateTime; +import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -7,25 +9,20 @@ import org.springframework.transaction.annotation.Transactional; import vaultWeb.models.ChatImage; -import java.time.OffsetDateTime; -import java.util.List; - @Repository public interface ChatImageRepo extends CrudRepository { + @Transactional + @Query( + value = + "INSERT INTO chat_images (image_content, receiver_id, sender_id, createdon) " + + "VALUES (CAST(:imageData AS bytea), :receiverId, :senderId, :createdon) RETURNING id", + nativeQuery = true) + Long saveImage( + @Param("imageData") byte[] imageData, + @Param("senderId") Integer senderId, + @Param("receiverId") Integer receiverId, + @Param("createdon") OffsetDateTime createdon); - @Transactional - @Query( - value = "INSERT INTO chat_images (image_content, receiver_id, sender_id, createdon) " + - "VALUES (CAST(:imageData AS bytea), :receiverId, :senderId, :createdon) RETURNING id", - nativeQuery = true - ) - Long saveImage( - @Param("imageData") byte[] imageData, - @Param("senderId") Integer senderId, - @Param("receiverId") Integer receiverId, - @Param("createdon") OffsetDateTime createdon - ); - - List createdon(OffsetDateTime createdon); + List createdon(OffsetDateTime createdon); } diff --git a/backend/src/main/java/vaultWeb/services/ChatImageService.java b/backend/src/main/java/vaultWeb/services/ChatImageService.java index 3ef38c48d..8f2474cf1 100644 --- a/backend/src/main/java/vaultWeb/services/ChatImageService.java +++ b/backend/src/main/java/vaultWeb/services/ChatImageService.java @@ -38,10 +38,12 @@ public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer r userRepository .findById(Long.valueOf(senderUserId)) - .orElseThrow(() -> new UserNotFoundException("Sender with id " + senderUserId + " not found")); + .orElseThrow( + () -> new UserNotFoundException("Sender with id " + senderUserId + " not found")); userRepository .findById(Long.valueOf(receiverUserId)) - .orElseThrow(() -> new UserNotFoundException("Receiver with id " + receiverUserId + " not found")); + .orElseThrow( + () -> new UserNotFoundException("Receiver with id " + receiverUserId + " not found")); OffsetDateTime createdon = OffsetDateTime.now(); Long ref = chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); From 387d677c428d888a3b584317d959872647a11bd3 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 10 Dec 2025 17:59:00 +0530 Subject: [PATCH 6/9] copilot review resolved --- .../controllers/ChatImageController.java | 38 ++++++++++++------- .../dtos/ChatImageUploadResponse.java | 11 ++++++ .../exceptions/GlobalExceptionHandler.java | 4 +- .../vaultWeb/services/ChatImageService.java | 6 +-- 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 backend/src/main/java/vaultWeb/dtos/ChatImageUploadResponse.java diff --git a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java index d891560ad..df6ffc509 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java @@ -7,6 +7,7 @@ import java.util.HashSet; import java.util.Set; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -16,14 +17,19 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import vaultWeb.services.ChatImageService; +import vaultWeb.services.auth.AuthService; +import vaultWeb.models.User; +import vaultWeb.dtos.ChatImageUploadResponse; @RestController @RequestMapping("/api/chat") @Tag(name = "Chat Image Controller", description = "Handles image uploads for chat messages") @RequiredArgsConstructor +@Slf4j public class ChatImageController { private final ChatImageService chatImageService; + private final AuthService authService; @Value("${vault.chatImage.maxSizeBytes:5242880}") // default 5 MB private long maxFileSizeBytes; @@ -39,33 +45,37 @@ public class ChatImageController { * sender/receiver users. * * @param imageFile the uploaded multipart image file (field name: {@code image}) - * @param senderUserId the ID of the sending user (must not be null) + * The sender is derived from the currently authenticated user; clients must NOT provide it. + * * @param receiverUserId the ID of the receiving user (must not be null) - * @return a 200 OK response containing an implementation-specific image reference string + * @return a 200 OK response containing a structured JSON body with the new image ID * @throws IllegalArgumentException if the file is empty/invalid or if IDs are null */ @PostMapping(value = "/upload-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation( summary = "Upload Chat Image", - description = "Uploads an image to be used in chat messages.") - public ResponseEntity uploadChatImage( + description = "Uploads an image to be used in chat messages. The sender is the currently authenticated user.") + public ResponseEntity uploadChatImage( @RequestParam("image") MultipartFile imageFile, - @RequestParam Integer senderUserId, @RequestParam Integer receiverUserId) { try { - if (senderUserId == null) { - throw new IllegalArgumentException("senderUserId must not be null"); - } - if (receiverUserId == null) { - throw new IllegalArgumentException("receiverUserId must not be null"); + // Derive sender from the authenticated user + User currentUser = authService.getCurrentUser(); + if (currentUser == null) { + return ResponseEntity.status(401) + .body(new ChatImageUploadResponse("Unauthorized: user is not authenticated", null)); } validateImage(imageFile); byte[] imageByteArray = imageFile.getBytes(); - String imageRef = - chatImageService.uploadChatImage(imageByteArray, senderUserId, receiverUserId); - return ResponseEntity.ok(imageRef); + Long imageId = + chatImageService.uploadChatImage(imageByteArray, currentUser.getId().intValue(), receiverUserId); + ChatImageUploadResponse body = new ChatImageUploadResponse("Image uploaded successfully", imageId); + return ResponseEntity.ok(body); } catch (IOException e) { - throw new RuntimeException(e); + // Log the root cause for diagnostics while returning a stable, user-friendly message + log.error("Failed to read/process uploaded image file", e); + return ResponseEntity.status(500) + .body(new ChatImageUploadResponse("Failed to process image file", null)); } } diff --git a/backend/src/main/java/vaultWeb/dtos/ChatImageUploadResponse.java b/backend/src/main/java/vaultWeb/dtos/ChatImageUploadResponse.java new file mode 100644 index 000000000..799561bf6 --- /dev/null +++ b/backend/src/main/java/vaultWeb/dtos/ChatImageUploadResponse.java @@ -0,0 +1,11 @@ +package vaultWeb.dtos; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ChatImageUploadResponse { + private String message; + private Long imageId; +} diff --git a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java index b55cdca51..9094c45c0 100644 --- a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java +++ b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java @@ -117,7 +117,9 @@ public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) /** Handles MaxUploadSizeExceededException (multipart too large) and returns 400 Bad Request. */ @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity handleMaxUploadSize(MaxUploadSizeExceededException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("File too large: " + ex.getMessage()); + // Provide a concise, user-friendly message without exposing internal details + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body("File size exceeds the maximum allowed limit of 5MB"); } /** Handles any other RuntimeException and returns 500 Internal Server Error. */ diff --git a/backend/src/main/java/vaultWeb/services/ChatImageService.java b/backend/src/main/java/vaultWeb/services/ChatImageService.java index 8f2474cf1..1362198b1 100644 --- a/backend/src/main/java/vaultWeb/services/ChatImageService.java +++ b/backend/src/main/java/vaultWeb/services/ChatImageService.java @@ -24,11 +24,11 @@ public class ChatImageService { * @param imageBytes raw image bytes to store (already validated for size/type upstream) * @param senderUserId ID of the user sending the image (must not be null; must exist) * @param receiverUserId ID of the user receiving the image (must not be null; must exist) - * @return a human-readable reference string that includes the generated image record ID + * @return the generated image record ID * @throws IllegalArgumentException if either user ID is null * @throws UserNotFoundException if either the sender or receiver cannot be found */ - public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer receiverUserId) { + public Long uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer receiverUserId) { if (senderUserId == null) { throw new IllegalArgumentException("senderUserId must not be null"); } @@ -47,6 +47,6 @@ public String uploadChatImage(byte[] imageBytes, Integer senderUserId, Integer r OffsetDateTime createdon = OffsetDateTime.now(); Long ref = chatImageRepo.saveImage(imageBytes, senderUserId, receiverUserId, createdon); - return "Image uploaded successfully with ref ID: " + ref; + return ref; } } From c80a0736d40654f948d49f4944141733b59ad598 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 10 Dec 2025 18:23:02 +0530 Subject: [PATCH 7/9] copilot review resolved --- .../controllers/ChatImageController.java | 51 ++++++++++++++++--- .../exceptions/GlobalExceptionHandler.java | 37 +++++++++++++- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java index df6ffc509..76ad2cd8f 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java @@ -65,8 +65,7 @@ public ResponseEntity uploadChatImage( return ResponseEntity.status(401) .body(new ChatImageUploadResponse("Unauthorized: user is not authenticated", null)); } - validateImage(imageFile); - byte[] imageByteArray = imageFile.getBytes(); + byte[] imageByteArray = validateAndReadImage(imageFile); Long imageId = chatImageService.uploadChatImage(imageByteArray, currentUser.getId().intValue(), receiverUserId); ChatImageUploadResponse body = new ChatImageUploadResponse("Image uploaded successfully", imageId); @@ -79,7 +78,7 @@ public ResponseEntity uploadChatImage( } } - private void validateImage(MultipartFile file) { + private byte[] validateAndReadImage(MultipartFile file) throws IOException { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("Image file cannot be empty"); } @@ -87,10 +86,50 @@ private void validateImage(MultipartFile file) { throw new IllegalArgumentException("Image file too large"); } - String contentType = file.getContentType(); + // Read the bytes only once and validate using magic bytes (server-side detection) + byte[] bytes = file.getBytes(); + + String detectedMime = detectMimeType(bytes); Set allowed = new HashSet<>(Arrays.asList(allowedMimeTypesProp.split(","))); - if (contentType == null || !allowed.contains(contentType)) { - throw new IllegalArgumentException("Unsupported image type. Allowed: " + allowed); + if (detectedMime == null || !allowed.contains(detectedMime)) { + throw new IllegalArgumentException( + "Unsupported image type. Detected: " + detectedMime + ". Allowed: " + allowed); + } + + return bytes; + } + + // Very small, fast signature checks for common image formats + private String detectMimeType(byte[] bytes) { + if (bytes == null || bytes.length < 12) { + return null; + } + + // JPEG: FF D8 FF + if ((bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xD8 && (bytes[2] & 0xFF) == 0xFF) { + return "image/jpeg"; } + + // PNG: 89 50 4E 47 0D 0A 1A 0A + if ((bytes[0] & 0xFF) == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 + && bytes[4] == 0x0D && bytes[5] == 0x0A && bytes[6] == 0x1A && bytes[7] == 0x0A) { + return "image/png"; + } + + // GIF: "GIF87a" or "GIF89a" + if (bytes.length >= 6) { + String sig = new String(bytes, 0, 6); + if ("GIF87a".equals(sig) || "GIF89a".equals(sig)) { + return "image/gif"; + } + } + + // WEBP: RIFF....WEBP + if (bytes[0] == 'R' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == 'F' + && bytes[8] == 'W' && bytes[9] == 'E' && bytes[10] == 'B' && bytes[11] == 'P') { + return "image/webp"; + } + + return null; } } diff --git a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java index 9094c45c0..27775652f 100644 --- a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java +++ b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java @@ -1,12 +1,14 @@ package vaultWeb.exceptions; import java.nio.file.AccessDeniedException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.util.unit.DataSize; import vaultWeb.exceptions.notfound.GroupNotFoundException; import vaultWeb.exceptions.notfound.NotMemberException; import vaultWeb.exceptions.notfound.UserNotFoundException; @@ -118,8 +120,12 @@ public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity handleMaxUploadSize(MaxUploadSizeExceededException ex) { // Provide a concise, user-friendly message without exposing internal details - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body("File size exceeds the maximum allowed limit of 5MB"); + String sizeLabel = resolveConfiguredMaxUploadSizeLabel(); + String message = + (sizeLabel != null) + ? "File size exceeds the maximum allowed limit of " + sizeLabel + : "File size exceeds the maximum allowed limit"; + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message); } /** Handles any other RuntimeException and returns 500 Internal Server Error. */ @@ -128,4 +134,31 @@ public ResponseEntity handleRuntimeException(RuntimeException ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Internal error: " + ex.getMessage()); } + + // --- Helpers --- + + @Value("${spring.servlet.multipart.max-file-size:}") + private String maxUploadSizeProp; + + private String resolveConfiguredMaxUploadSizeLabel() { + try { + if (maxUploadSizeProp == null || maxUploadSizeProp.isBlank()) { + return null; + } + DataSize size = DataSize.parse(maxUploadSizeProp.trim()); + long bytes = size.toBytes(); + // Prefer MB if evenly divisible, then KB, otherwise bytes + long mb = bytes / (1024 * 1024); + if (mb > 0 && (bytes % (1024 * 1024) == 0)) { + return mb + "MB"; + } + long kb = bytes / 1024; + if (kb > 0 && (bytes % 1024 == 0)) { + return kb + "KB"; + } + return bytes + "B"; + } catch (Exception e) { + return null; // Fallback to generic message on parse issues + } + } } From 792a662c5879f0d78d8f248fb56e3dbcdb6481fa Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 10 Dec 2025 21:01:02 +0530 Subject: [PATCH 8/9] spotless build successfull --- .../controllers/ChatImageController.java | 41 ++++++++++++------- .../exceptions/GlobalExceptionHandler.java | 2 +- .../src/main/resources/application.properties | 4 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java index 76ad2cd8f..75d21a6c0 100644 --- a/backend/src/main/java/vaultWeb/controllers/ChatImageController.java +++ b/backend/src/main/java/vaultWeb/controllers/ChatImageController.java @@ -16,10 +16,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import vaultWeb.dtos.ChatImageUploadResponse; +import vaultWeb.models.User; import vaultWeb.services.ChatImageService; import vaultWeb.services.auth.AuthService; -import vaultWeb.models.User; -import vaultWeb.dtos.ChatImageUploadResponse; @RestController @RequestMapping("/api/chat") @@ -44,9 +44,8 @@ public class ChatImageController { * and delegates to {@link vaultWeb.services.ChatImageService} to persist the image and validate * sender/receiver users. * - * @param imageFile the uploaded multipart image file (field name: {@code image}) - * The sender is derived from the currently authenticated user; clients must NOT provide it. - * + * @param imageFile the uploaded multipart image file (field name: {@code image}) The sender is + * derived from the currently authenticated user; clients must NOT provide it. * @param receiverUserId the ID of the receiving user (must not be null) * @return a 200 OK response containing a structured JSON body with the new image ID * @throws IllegalArgumentException if the file is empty/invalid or if IDs are null @@ -54,10 +53,10 @@ public class ChatImageController { @PostMapping(value = "/upload-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation( summary = "Upload Chat Image", - description = "Uploads an image to be used in chat messages. The sender is the currently authenticated user.") + description = + "Uploads an image to be used in chat messages. The sender is the currently authenticated user.") public ResponseEntity uploadChatImage( - @RequestParam("image") MultipartFile imageFile, - @RequestParam Integer receiverUserId) { + @RequestParam("image") MultipartFile imageFile, @RequestParam Integer receiverUserId) { try { // Derive sender from the authenticated user User currentUser = authService.getCurrentUser(); @@ -67,8 +66,10 @@ public ResponseEntity uploadChatImage( } byte[] imageByteArray = validateAndReadImage(imageFile); Long imageId = - chatImageService.uploadChatImage(imageByteArray, currentUser.getId().intValue(), receiverUserId); - ChatImageUploadResponse body = new ChatImageUploadResponse("Image uploaded successfully", imageId); + chatImageService.uploadChatImage( + imageByteArray, currentUser.getId().intValue(), receiverUserId); + ChatImageUploadResponse body = + new ChatImageUploadResponse("Image uploaded successfully", imageId); return ResponseEntity.ok(body); } catch (IOException e) { // Log the root cause for diagnostics while returning a stable, user-friendly message @@ -111,8 +112,14 @@ private String detectMimeType(byte[] bytes) { } // PNG: 89 50 4E 47 0D 0A 1A 0A - if ((bytes[0] & 0xFF) == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 - && bytes[4] == 0x0D && bytes[5] == 0x0A && bytes[6] == 0x1A && bytes[7] == 0x0A) { + if ((bytes[0] & 0xFF) == 0x89 + && bytes[1] == 0x50 + && bytes[2] == 0x4E + && bytes[3] == 0x47 + && bytes[4] == 0x0D + && bytes[5] == 0x0A + && bytes[6] == 0x1A + && bytes[7] == 0x0A) { return "image/png"; } @@ -125,8 +132,14 @@ private String detectMimeType(byte[] bytes) { } // WEBP: RIFF....WEBP - if (bytes[0] == 'R' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == 'F' - && bytes[8] == 'W' && bytes[9] == 'E' && bytes[10] == 'B' && bytes[11] == 'P') { + if (bytes[0] == 'R' + && bytes[1] == 'I' + && bytes[2] == 'F' + && bytes[3] == 'F' + && bytes[8] == 'W' + && bytes[9] == 'E' + && bytes[10] == 'B' + && bytes[11] == 'P') { return "image/webp"; } diff --git a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java index 27775652f..f0490362a 100644 --- a/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java +++ b/backend/src/main/java/vaultWeb/exceptions/GlobalExceptionHandler.java @@ -5,10 +5,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.util.unit.DataSize; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MaxUploadSizeExceededException; -import org.springframework.util.unit.DataSize; import vaultWeb.exceptions.notfound.GroupNotFoundException; import vaultWeb.exceptions.notfound.NotMemberException; import vaultWeb.exceptions.notfound.UserNotFoundException; diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4fee01e62..2c00eb5d2 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=backend server.port=8080 # PostgreSQL DB Connection -spring.datasource.username=postgres -spring.datasource.password=root +spring.datasource.username=vault_user +spring.datasource.password=vault_password spring.datasource.url=jdbc:postgresql://localhost:5432/vaultdb spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true From 7f0906b1d9280b99f95da58b0330c828ebe3905d Mon Sep 17 00:00:00 2001 From: Abhishek Date: Wed, 10 Dec 2025 21:01:29 +0530 Subject: [PATCH 9/9] spotless build successfull --- backend/src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 2c00eb5d2..4fee01e62 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=backend server.port=8080 # PostgreSQL DB Connection -spring.datasource.username=vault_user -spring.datasource.password=vault_password +spring.datasource.username=postgres +spring.datasource.password=root spring.datasource.url=jdbc:postgresql://localhost:5432/vaultdb spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true