From a7ef4a56cfb2734e0e4e81e373c59ae1fdf85abf Mon Sep 17 00:00:00 2001 From: Bavithbabu Date: Tue, 24 Mar 2026 17:50:10 +0530 Subject: [PATCH] Done with image_enhancment --- .../exception/ExceptionController.java | 217 ++++++++++-------- .../post/controller/PostController.java | 29 ++- .../post/service/PostServiceImp.java | 14 ++ src/main/resources/application.properties | 156 ++++++------- 4 files changed, 228 insertions(+), 188 deletions(-) diff --git a/src/main/java/com/cadac/stone_inscription/exception/ExceptionController.java b/src/main/java/com/cadac/stone_inscription/exception/ExceptionController.java index 01cba7c..6fd9466 100644 --- a/src/main/java/com/cadac/stone_inscription/exception/ExceptionController.java +++ b/src/main/java/com/cadac/stone_inscription/exception/ExceptionController.java @@ -1,102 +1,115 @@ -package com.cadac.stone_inscription.exception; - -import org.springframework.validation.BindException; - -import java.nio.file.AccessDeniedException; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@RestControllerAdvice -public class ExceptionController { - - // Custom Exception - @ExceptionHandler(StoneInscriptionException.class) - public ResponseEntity> handleStoneInscriptionException(StoneInscriptionException exception) { - Map errorResp = new HashMap(); - errorResp.put("error_message", exception.getMessage()); - errorResp.put("http_status", exception.getHttpStatus()); - errorResp.put("http_status_code", exception.getHttpStatus().value()); - return new ResponseEntity>(errorResp, exception.getHttpStatus()); - } - - // Request Body - @ExceptionHandler(MethodArgumentNotValidException.class) - - public ResponseEntity> handleMethodArgumentNotValidException( - MethodArgumentNotValidException exeption) { - - Map errorResp = new HashMap(); - - exeption.getBindingResult().getAllErrors().forEach((error) -> { - String fieldName = ((FieldError) error).getField(); - String message = error.getDefaultMessage(); - // errorResp.put(fieldName, message); - errorResp.put("error_message", fieldName + " --> " + message); - }); - - errorResp.put("http_status", HttpStatus.UNPROCESSABLE_ENTITY.value()); - return new ResponseEntity>(errorResp, HttpStatus.UNPROCESSABLE_ENTITY); - - } - - // Model Attribute - @ExceptionHandler(BindException.class) - - public ResponseEntity> handleBindException( - BindException exeption) { - - Map errorResp = new HashMap(); - errorResp.put("http_status", HttpStatus.UNPROCESSABLE_ENTITY.value()); - exeption.getBindingResult().getAllErrors().forEach((error) -> { - String fieldName = ((FieldError) error).getField(); - String message = error.getDefaultMessage(); - errorResp.put("error_message", fieldName + " : " + message); - }); - - return new ResponseEntity>(errorResp, HttpStatus.UNPROCESSABLE_ENTITY); - - } - - @ExceptionHandler(HttpMessageNotReadableException.class) - public ResponseEntity> handleHttpMessageNotReadableException( - HttpMessageNotReadableException exception) { - - Map errorResp = new HashMap(); - errorResp.put("error_message", exception.getMessage()); - errorResp.put("http_status", HttpStatus.BAD_REQUEST); - errorResp.put("http_status_code", HttpStatus.BAD_REQUEST.value()); - return new ResponseEntity>(errorResp, HttpStatus.BAD_REQUEST); - } - - - // // 🔹 500 Internal Server Error (catch all) - @ExceptionHandler(Exception.class) - public ResponseEntity> handleGeneralException(Exception ex) { - Map errorResp = new HashMap(); - errorResp.put("error_message", ex.getMessage()); - errorResp.put("http_status", HttpStatus.BAD_REQUEST); - errorResp.put("http_status_code", HttpStatus.BAD_REQUEST.value()); - return new ResponseEntity>(errorResp, HttpStatus.INTERNAL_SERVER_ERROR); - - } - - // // Any kind of Exception - // @ExceptionHandler(Exception.class) - // public ResponseEntity handleUnknownException(Exception ex) { - - // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - // .body("An unknown error occurred. Please try again later."); - // } - -} +package com.cadac.stone_inscription.exception; + +import org.springframework.validation.BindException; + +import java.nio.file.AccessDeniedException; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authorization.AuthorizationDeniedException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ExceptionController { + + // Custom Exception + @ExceptionHandler(StoneInscriptionException.class) + public ResponseEntity> handleStoneInscriptionException(StoneInscriptionException exception) { + Map errorResp = new HashMap(); + errorResp.put("error_message", exception.getMessage()); + errorResp.put("http_status", exception.getHttpStatus()); + errorResp.put("http_status_code", exception.getHttpStatus().value()); + return new ResponseEntity>(errorResp, exception.getHttpStatus()); + } + + // Request Body + @ExceptionHandler(MethodArgumentNotValidException.class) + + public ResponseEntity> handleMethodArgumentNotValidException( + MethodArgumentNotValidException exeption) { + + Map errorResp = new HashMap(); + + exeption.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String message = error.getDefaultMessage(); + // errorResp.put(fieldName, message); + errorResp.put("error_message", fieldName + " --> " + message); + }); + + errorResp.put("http_status", HttpStatus.UNPROCESSABLE_ENTITY.value()); + return new ResponseEntity>(errorResp, HttpStatus.UNPROCESSABLE_ENTITY); + + } + + // Model Attribute + @ExceptionHandler(BindException.class) + + public ResponseEntity> handleBindException( + BindException exeption) { + + Map errorResp = new HashMap(); + errorResp.put("http_status", HttpStatus.UNPROCESSABLE_ENTITY.value()); + exeption.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String message = error.getDefaultMessage(); + errorResp.put("error_message", fieldName + " : " + message); + }); + + return new ResponseEntity>(errorResp, HttpStatus.UNPROCESSABLE_ENTITY); + + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleHttpMessageNotReadableException( + HttpMessageNotReadableException exception) { + + Map errorResp = new HashMap(); + errorResp.put("error_message", exception.getMessage()); + errorResp.put("http_status", HttpStatus.BAD_REQUEST); + errorResp.put("http_status_code", HttpStatus.BAD_REQUEST.value()); + return new ResponseEntity>(errorResp, HttpStatus.BAD_REQUEST); + } + + + + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity> handleMaxUploadSizeExceededException( + MaxUploadSizeExceededException exception) { + + Map errorResp = new HashMap(); + errorResp.put("error_message", "Upload size exceeded the allowed limit. Each image must be 75 MB or less and a post can contain at most 16 images."); + errorResp.put("http_status", HttpStatus.BAD_REQUEST); + errorResp.put("http_status_code", HttpStatus.BAD_REQUEST.value()); + return new ResponseEntity>(errorResp, HttpStatus.BAD_REQUEST); + } + + // // 🔹 500 Internal Server Error (catch all) + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneralException(Exception ex) { + Map errorResp = new HashMap(); + errorResp.put("error_message", ex.getMessage()); + errorResp.put("http_status", HttpStatus.BAD_REQUEST); + errorResp.put("http_status_code", HttpStatus.BAD_REQUEST.value()); + return new ResponseEntity>(errorResp, HttpStatus.INTERNAL_SERVER_ERROR); + + } + + // // Any kind of Exception + // @ExceptionHandler(Exception.class) + // public ResponseEntity handleUnknownException(Exception ex) { + + // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + // .body("An unknown error occurred. Please try again later."); + // } + +} diff --git a/src/main/java/com/cadac/stone_inscription/post/controller/PostController.java b/src/main/java/com/cadac/stone_inscription/post/controller/PostController.java index 1adc615..f8fbabc 100644 --- a/src/main/java/com/cadac/stone_inscription/post/controller/PostController.java +++ b/src/main/java/com/cadac/stone_inscription/post/controller/PostController.java @@ -31,6 +31,9 @@ @RequestMapping("/post") public class PostController { + private static final int MAX_IMAGES_PER_POST = 16; + private static final long MAX_IMAGE_SIZE_BYTES = 75L * 1024 * 1024; + @Autowired private PostService postService; @@ -52,7 +55,7 @@ public ResponseEntity addPostWithFile( throw new StoneInscriptionException("No File Uploaded", HttpStatus.BAD_REQUEST); } - validateFiles(files); + validateFiles(files, MAX_IMAGES_PER_POST); String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); @@ -208,7 +211,7 @@ public ResponseEntity updatePost(HttpServletRequest request, @RequestPart(value = "files", required = false) MultipartFile... files) { files = getNonEmptyFiles(files); - validateFiles(files); + validateFiles(files, MAX_IMAGES_PER_POST); String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { @@ -232,7 +235,7 @@ public ResponseEntity addImagesToPost(HttpServletRequest request, throw new StoneInscriptionException("No File Uploaded", HttpStatus.BAD_REQUEST); } - validateFiles(files); + validateFiles(files, MAX_IMAGES_PER_POST); String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { @@ -261,7 +264,7 @@ public ResponseEntity deleteImagesFromPost(HttpServletRequest request, // TODO: Remove these methods when testing is done to restore normal behavior. // ============================ - // @PostMapping("/test/updatePost/{email}") + // // @PostMapping("/test/updatePost/{email}") // public ResponseEntity updatePostForTest( // @PathVariable String email, // @RequestPart(value = "post", required = false) InscriptionPostDto InscriptionPostDto, @@ -270,7 +273,7 @@ public ResponseEntity deleteImagesFromPost(HttpServletRequest request, // @RequestPart(value = "files", required = false) MultipartFile... files) { // files = getNonEmptyFiles(files); - // validateFiles(files); + // validateFiles(files, MAX_IMAGES_PER_POST); // return postService.updatePost(email, InscriptionPostDto, postId, deletedImageIds, files); // } @@ -287,7 +290,7 @@ public ResponseEntity deleteImagesFromPost(HttpServletRequest request, // throw new StoneInscriptionException("No File Uploaded", HttpStatus.BAD_REQUEST); // } - // validateFiles(files); + // validateFiles(files, MAX_IMAGES_PER_POST); // return postService.addPostWithFile(InscriptionPostDto, files, email); // } @@ -304,7 +307,7 @@ public ResponseEntity deleteImagesFromPost(HttpServletRequest request, // throw new StoneInscriptionException("No File Uploaded", HttpStatus.BAD_REQUEST); // } - // validateFiles(files); + // validateFiles(files, MAX_IMAGES_PER_POST); // return postService.addImagesToPost(email, postId, files); // } @@ -338,7 +341,12 @@ private MultipartFile[] getNonEmptyFiles(MultipartFile[] files) { .toArray(MultipartFile[]::new); } - private void validateFiles(MultipartFile[] files) { + private void validateFiles(MultipartFile[] files, int maxFilesAllowed) { + if (files.length > maxFilesAllowed) { + throw new StoneInscriptionException("Maximum " + maxFilesAllowed + " images are allowed per post", + HttpStatus.BAD_REQUEST); + } + Arrays.stream(files).forEach(file -> { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || Arrays.stream(fileExt) @@ -347,6 +355,11 @@ private void validateFiles(MultipartFile[] files) { throw new StoneInscriptionException("Invalid File format only allowed" + Arrays.toString(fileExt), HttpStatus.BAD_REQUEST); } + + if (file.getSize() > MAX_IMAGE_SIZE_BYTES) { + throw new StoneInscriptionException("Each image size should be less than or equal to 75 MB", + HttpStatus.BAD_REQUEST); + } }); } diff --git a/src/main/java/com/cadac/stone_inscription/post/service/PostServiceImp.java b/src/main/java/com/cadac/stone_inscription/post/service/PostServiceImp.java index c145fbf..ee72fe3 100644 --- a/src/main/java/com/cadac/stone_inscription/post/service/PostServiceImp.java +++ b/src/main/java/com/cadac/stone_inscription/post/service/PostServiceImp.java @@ -45,6 +45,8 @@ @Service public class PostServiceImp implements PostService { + private static final int MAX_IMAGES_PER_POST = 16; + @Autowired private UserRepository userRepository; @@ -111,6 +113,7 @@ public ResponseEntity addPostWithFile(InscriptionPostDto inscriptionPostDto, } inscriptionPost.getDescription().setModeration(moderation); + ensureMaximumImageCount(0, 0, ls.size()); adjustUserImagesUploaded(user, ls.size()); userRepository.save(user); @@ -400,6 +403,7 @@ public ResponseEntity updatePost(String usernameFromToken, InscriptionPostDto List newImages = validateAndExtractImages(files, user.getId(), deletableImageIds, false); ensureMinimumImageCount(existingImageIds.size(), deletableImageIds.size(), newImages.size()); + ensureMaximumImageCount(existingImageIds.size(), deletableImageIds.size(), newImages.size()); if (inscriptionPostDto != null) { ContentModeration moderation = moderatePostContent( @@ -441,6 +445,7 @@ public ResponseEntity addImagesToPost(String usernameFromToken, String postId List newImages = validateAndExtractImages(files, user.getId(), Collections.emptySet(), true); List updatedImageIds = getExistingImageIds(post); + ensureMaximumImageCount(updatedImageIds.size(), 0, newImages.size()); updatedImageIds.addAll(saveImages(post.getId(), newImages)); updatePostImages(post, updatedImageIds, Collections.emptySet()); @@ -708,6 +713,15 @@ private void ensureMinimumImageCount(int existingImageCount, int deletedImageCou } } + private void ensureMaximumImageCount(int existingImageCount, int deletedImageCount, int newImageCount) { + int finalImageCount = existingImageCount - deletedImageCount + newImageCount; + + if (finalImageCount > MAX_IMAGES_PER_POST) { + throw new StoneInscriptionException("Maximum " + MAX_IMAGES_PER_POST + " images are allowed per post", + HttpStatus.BAD_REQUEST); + } + } + private List removeDeletedImageIds(List existingImageIds, Set deletableImageIds) { return existingImageIds.stream() .filter(imageId -> !deletableImageIds.contains(imageId)) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a784351..663ec77 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,78 +1,78 @@ -# Application Name -spring.application.name=stone-inscription - - -# Server port -server.port=8080 - -# MongoDB connection -spring.data.mongodb.uri=${MONGO_URI} -# spring.data.mongodb.uri= mongodb://localhost:27017/StoneInscription -# mongodb://inscriptions.cdacb.in:27017/StoneInscription -# mongodb://root:password123@mongodb-primary:27017,mongodb-secondary:27017/StoneInscription?replicaSet=replicaset&authSource=admin - -# CORS configuration -app.cors.url=https://inscriptions.cdacb.in - -app.backend.url= https://inscriptions.cdacb.in/api - -geolocation.api.url=https://nominatim.openstreetmap.org/reverse - - -# JWT configuration - -file.extn = .png, .jpeg, .jpg , .webm, .webp - -# Basic Spring Security and OAuth2 logging -logging.level.org.springframework.security=DEBUG -logging.level.org.springframework.security.oauth2=DEBUG -logging.level.org.springframework.security.web=DEBUG - -# Web request logging to see all incoming requests -logging.level.org.springframework.web=DEBUG -logging.level.org.springframework.web.servlet=DEBUG - -# Your application package logging (replace with your actual package) -logging.level.com.cdac.stoneinscription=DEBUG - -# HTTP client logging (for OAuth2 token exchanges) -logging.level.org.springframework.web.client.RestTemplate=DEBUG -logging.level.org.apache.http=DEBUG - -# Session management logging -logging.level.org.springframework.session=DEBUG - -# Filter chain logging to see exact filter execution order -logging.level.org.springframework.security.web.FilterChainProxy=TRACE - -# OAuth2 specific components -logging.level.org.springframework.security.oauth2.client=DEBUG -logging.level.org.springframework.security.oauth2.core=DEBUG - -# Include stack traces and method names in logs -logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}:%line] - %msg%n -logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}:%line] - %msg%n - -# Enable request/response logging -logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG - -# If you want to see method entry/exit (very verbose) -# logging.level.org.springframework.aop=TRACE -spring.servlet.multipart.max-file-size=75MB -spring.servlet.multipart.max-request-size=75MB - -management.endpoints.web.exposure.include=health,info,prometheus -management.endpoint.prometheus.enabled=true -management.metrics.export.prometheus.enabled=true - -# content.moderation.webhook-url=${CONTENT_MODERATION_WEBHOOK_URL} -# content.moderation.safe-threshold=${CONTENT_MODERATION_SAFE_THRESHOLD:0.7} -# content.moderation.connect-timeout-ms=${CONTENT_MODERATION_CONNECT_TIMEOUT_MS:5000} -# content.moderation.read-timeout-ms=${CONTENT_MODERATION_READ_TIMEOUT_MS:10000} -# content.moderation.insecure-ssl=${CONTENT_MODERATION_INSECURE_SSL:false} - -content.moderation.webhook-url=${CONTENT_MODERATION_WEBHOOK_URL} -content.moderation.safe-threshold=${CONTENT_MODERATION_SAFE_THRESHOLD} -content.moderation.connect-timeout-ms=${CONTENT_MODERATION_CONNECT_TIMEOUT_MS} -content.moderation.read-timeout-ms=${CONTENT_MODERATION_READ_TIMEOUT_MS} -content.moderation.insecure-ssl=${CONTENT_MODERATION_INSECURE_SSL} +# Application Name +spring.application.name=stone-inscription + + +# Server port +server.port=8080 + +# MongoDB connection +spring.data.mongodb.uri=${MONGO_URI} +# spring.data.mongodb.uri= mongodb://localhost:27017/StoneInscription +# mongodb://inscriptions.cdacb.in:27017/StoneInscription +# mongodb://root:password123@mongodb-primary:27017,mongodb-secondary:27017/StoneInscription?replicaSet=replicaset&authSource=admin + +# CORS configuration +app.cors.url=https://inscriptions.cdacb.in + +app.backend.url= https://inscriptions.cdacb.in/api + +geolocation.api.url=https://nominatim.openstreetmap.org/reverse + + +# JWT configuration + +file.extn = .png, .jpeg, .jpg , .webm, .webp + +# Basic Spring Security and OAuth2 logging +logging.level.org.springframework.security=DEBUG +logging.level.org.springframework.security.oauth2=DEBUG +logging.level.org.springframework.security.web=DEBUG + +# Web request logging to see all incoming requests +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web.servlet=DEBUG + +# Your application package logging (replace with your actual package) +logging.level.com.cdac.stoneinscription=DEBUG + +# HTTP client logging (for OAuth2 token exchanges) +logging.level.org.springframework.web.client.RestTemplate=DEBUG +logging.level.org.apache.http=DEBUG + +# Session management logging +logging.level.org.springframework.session=DEBUG + +# Filter chain logging to see exact filter execution order +logging.level.org.springframework.security.web.FilterChainProxy=TRACE + +# OAuth2 specific components +logging.level.org.springframework.security.oauth2.client=DEBUG +logging.level.org.springframework.security.oauth2.core=DEBUG + +# Include stack traces and method names in logs +logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}:%line] - %msg%n +logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}:%line] - %msg%n + +# Enable request/response logging +logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG + +# If you want to see method entry/exit (very verbose) +# logging.level.org.springframework.aop=TRACE +spring.servlet.multipart.max-file-size=75MB +spring.servlet.multipart.max-request-size=1200MB + +management.endpoints.web.exposure.include=health,info,prometheus +management.endpoint.prometheus.enabled=true +management.metrics.export.prometheus.enabled=true + +# content.moderation.webhook-url=${CONTENT_MODERATION_WEBHOOK_URL} +# content.moderation.safe-threshold=${CONTENT_MODERATION_SAFE_THRESHOLD:0.7} +# content.moderation.connect-timeout-ms=${CONTENT_MODERATION_CONNECT_TIMEOUT_MS:5000} +# content.moderation.read-timeout-ms=${CONTENT_MODERATION_READ_TIMEOUT_MS:10000} +# content.moderation.insecure-ssl=${CONTENT_MODERATION_INSECURE_SSL:false} + +content.moderation.webhook-url=${CONTENT_MODERATION_WEBHOOK_URL} +content.moderation.safe-threshold=${CONTENT_MODERATION_SAFE_THRESHOLD} +content.moderation.connect-timeout-ms=${CONTENT_MODERATION_CONNECT_TIMEOUT_MS} +content.moderation.read-timeout-ms=${CONTENT_MODERATION_READ_TIMEOUT_MS} +content.moderation.insecure-ssl=${CONTENT_MODERATION_INSECURE_SSL}