diff --git a/src/main/java/com/cadac/stone_inscription/auth/JwtRequestFilter.java b/src/main/java/com/cadac/stone_inscription/auth/JwtRequestFilter.java index 9642a5b..8cc4bf7 100644 --- a/src/main/java/com/cadac/stone_inscription/auth/JwtRequestFilter.java +++ b/src/main/java/com/cadac/stone_inscription/auth/JwtRequestFilter.java @@ -60,7 +60,7 @@ protected void doFilterInternal(HttpServletRequest request, } catch (Exception ex) { request.setAttribute("exception", ex); - + throw ex; } @@ -86,9 +86,8 @@ public String extractJwtFromRequest(HttpServletRequest request) { // } } else { - throw new StoneInscriptionException("Invalid Token Request Bearer not found ", - HttpStatus.BAD_REQUEST); - // return null; + throw new StoneInscriptionException("Invalid Token Request Bearer not found ", HttpStatus.BAD_REQUEST); + } } diff --git a/src/main/java/com/cadac/stone_inscription/configuration/StoneinscriptionConfiguration.java b/src/main/java/com/cadac/stone_inscription/configuration/StoneinscriptionConfiguration.java index be00b2a..2048a62 100644 --- a/src/main/java/com/cadac/stone_inscription/configuration/StoneinscriptionConfiguration.java +++ b/src/main/java/com/cadac/stone_inscription/configuration/StoneinscriptionConfiguration.java @@ -93,18 +93,15 @@ public SecurityFilterChain filterChain(HttpSecurity http, CustomOAuth2SuccessHandler successHandler) throws Exception { http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // ✅ Disable CSRF completely for JWT stateless API .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authz -> authz .requestMatchers("/api/v1/noauth/**", "/post/public/**").permitAll() - // TODO: PRODUCTION — uncomment .authenticated() and comment - // .permitAll() below .requestMatchers("/api/v1/**", "/post/**").authenticated() - // .requestMatchers("/api/v1/**", "/post/**").permitAll() // TESTING - // ONLY + // .requestMatchers("/api/v1/**", "/post/**").permitAll() .requestMatchers("/oauth2/**", "/oauth2/login/**").permitAll() .anyRequest().permitAll()) diff --git a/src/main/java/com/cadac/stone_inscription/entity/InscriptionPost.java b/src/main/java/com/cadac/stone_inscription/entity/InscriptionPost.java index 97baef2..af95540 100644 --- a/src/main/java/com/cadac/stone_inscription/entity/InscriptionPost.java +++ b/src/main/java/com/cadac/stone_inscription/entity/InscriptionPost.java @@ -1,14 +1,12 @@ package com.cadac.stone_inscription.entity; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - -import com.cadac.stone_inscription.moderation.model.ContentModeration; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.*; import org.bson.types.ObjectId; @@ -146,18 +144,14 @@ public static class Description { @JsonProperty("englishTranslation") private String englishTranslation; - @Field("upvote") - @JsonProperty("upvote") - @Builder.Default - private Integer upvote = 0; - - @Field("moderation") - @JsonProperty("moderation") - private ContentModeration moderation; - - @Field("geolocation") - @JsonProperty("geolocation") - private GeoLocation geolocation; + @Field("upvote") + @JsonProperty("upvote") + @Builder.Default + private Integer upvote = 0; + + @Field("geolocation") + @JsonProperty("geolocation") + private GeoLocation geolocation; @CreatedDate @Field("createdAt") diff --git a/src/main/java/com/cadac/stone_inscription/entity/PublicPostDescription.java b/src/main/java/com/cadac/stone_inscription/entity/PublicPostDescription.java index 3fc66b5..a9adc45 100644 --- a/src/main/java/com/cadac/stone_inscription/entity/PublicPostDescription.java +++ b/src/main/java/com/cadac/stone_inscription/entity/PublicPostDescription.java @@ -1,9 +1,8 @@ package com.cadac.stone_inscription.entity; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import com.cadac.stone_inscription.moderation.model.ContentModeration; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.*; import org.bson.types.ObjectId; @@ -52,19 +51,15 @@ public class PublicPostDescription { @JsonProperty("description") private String description; - @Field("upvote") - @JsonProperty("upvote") - @Builder.Default - private Integer upvote = 0; - - @Field("moderation") - @JsonProperty("moderation") - private ContentModeration moderation; - - @Field("uservote") - @JsonProperty("userVote") - @Builder.Default - private List userVote = new LinkedList<>(); + @Field("upvote") + @JsonProperty("upvote") + @Builder.Default + private Integer upvote = 0; + + @Field("uservote") + @JsonProperty("userVote") + @Builder.Default + private List userVote = new LinkedList<>(); @CreatedDate @Field("createdAt") diff --git a/src/main/java/com/cadac/stone_inscription/moderation/client/N8nModerationClient.java b/src/main/java/com/cadac/stone_inscription/moderation/client/N8nModerationClient.java deleted file mode 100644 index e020ea7..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/client/N8nModerationClient.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.cadac.stone_inscription.moderation.client; - -import java.security.GeneralSecurityException; -import java.security.SecureRandom; -import java.time.Duration; -import java.util.List; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import com.cadac.stone_inscription.moderation.config.ContentModerationProperties; -import com.cadac.stone_inscription.moderation.dto.ContentModerationRequestDto; - -@Component -public class N8nModerationClient { - - private final ContentModerationProperties properties; - private final RestTemplate restTemplate; - - public N8nModerationClient(ContentModerationProperties properties) { - this.properties = properties; - this.restTemplate = buildRestTemplate(properties); - } - - public String moderate(ContentModerationRequestDto request) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - return restTemplate.exchange( - properties.getWebhookUrl(), - HttpMethod.POST, - new HttpEntity<>(request, headers), - new ParameterizedTypeReference() { - }).getBody(); - } - - private RestTemplate buildRestTemplate(ContentModerationProperties properties) { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - requestFactory.setConnectTimeout(Duration.ofMillis(properties.getConnectTimeoutMs())); - requestFactory.setReadTimeout(Duration.ofMillis(properties.getReadTimeoutMs())); - - if (Boolean.TRUE.equals(properties.getInsecureSsl())) { - configureInsecureSsl(); - } - - return new RestTemplate(requestFactory); - } - - private void configureInsecureSsl() { - try { - TrustManager[] trustAllCerts = new TrustManager[] { - new X509TrustManager() { - @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - @Override - public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { - } - } - }; - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); - HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); - } catch (GeneralSecurityException ex) { - throw new IllegalStateException("Failed to configure insecure SSL for content moderation", ex); - } - } -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/config/ContentModerationProperties.java b/src/main/java/com/cadac/stone_inscription/moderation/config/ContentModerationProperties.java deleted file mode 100644 index a2cff7e..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/config/ContentModerationProperties.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.cadac.stone_inscription.moderation.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Component -@ConfigurationProperties(prefix = "content.moderation") -public class ContentModerationProperties { - - private String webhookUrl; - - private Double safeThreshold = 0.7; - - private Integer connectTimeoutMs = 5000; - - private Integer readTimeoutMs = 10000; - - private Boolean insecureSsl = false; -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationRequestDto.java b/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationRequestDto.java deleted file mode 100644 index 4ec8632..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationRequestDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.cadac.stone_inscription.moderation.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ContentModerationRequestDto { - - @JsonProperty("title") - private String title; - - @JsonProperty("topic") - private String topic; - - @JsonProperty("description") - private String description; -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationResponseDto.java b/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationResponseDto.java deleted file mode 100644 index 0d37a47..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/dto/ContentModerationResponseDto.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.cadac.stone_inscription.moderation.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class ContentModerationResponseDto { - - @JsonProperty("timestamp") - private String timestamp; - - @JsonProperty("decision") - private String decision; - - @JsonProperty("label") - private String label; - - @JsonProperty("confidence") - private Double confidence; - - @JsonProperty("reason") - private String reason; - - @JsonProperty("status") - private String status; - - @JsonProperty("description") - private String description; - - @JsonProperty("id") - private Long id; - - @JsonProperty("createdAt") - private String createdAt; - - @JsonProperty("updatedAt") - private String updatedAt; -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModeration.java b/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModeration.java deleted file mode 100644 index 7d7bafd..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModeration.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cadac.stone_inscription.moderation.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ContentModeration { - - @JsonProperty("label") - private String label; - - @JsonProperty("confidence") - private Double confidence; - - @JsonProperty("decision") - private String decision; - - @JsonProperty("status") - private String status; - - @JsonProperty("reason") - private String reason; -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModerationResult.java b/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModerationResult.java deleted file mode 100644 index a25cfda..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/model/ContentModerationResult.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.cadac.stone_inscription.moderation.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ContentModerationResult { - - private boolean approved; - - private String label; - - private Double confidence; - - private String decision; - - private String status; - - private String reason; - - public ContentModeration toContentModeration() { - return ContentModeration.builder() - .label(label) - .confidence(confidence) - .decision(decision) - .status(status) - .reason(reason) - .build(); - } -} diff --git a/src/main/java/com/cadac/stone_inscription/moderation/service/ContentModerationService.java b/src/main/java/com/cadac/stone_inscription/moderation/service/ContentModerationService.java deleted file mode 100644 index 49be6b6..0000000 --- a/src/main/java/com/cadac/stone_inscription/moderation/service/ContentModerationService.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.cadac.stone_inscription.moderation.service; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestClientResponseException; - -import com.cadac.stone_inscription.exception.StoneInscriptionException; -import com.cadac.stone_inscription.moderation.client.N8nModerationClient; -import com.cadac.stone_inscription.moderation.config.ContentModerationProperties; -import com.cadac.stone_inscription.moderation.dto.ContentModerationRequestDto; -import com.cadac.stone_inscription.moderation.dto.ContentModerationResponseDto; -import com.cadac.stone_inscription.moderation.model.ContentModerationResult; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - - -@Service -public class ContentModerationService { - - private static final Logger log = LoggerFactory.getLogger(ContentModerationService.class); - - private final N8nModerationClient n8nModerationClient; - private final ContentModerationProperties properties; - private final ObjectMapper objectMapper; - - public ContentModerationService(N8nModerationClient n8nModerationClient, - ContentModerationProperties properties) { - this.n8nModerationClient = n8nModerationClient; - this.properties = properties; - this.objectMapper = new ObjectMapper(); - } - - public ContentModerationResult moderate(String title, String topic, String description) { - validateRequest(title, topic, description); - - String rawResponse; - try { - rawResponse = n8nModerationClient.moderate(ContentModerationRequestDto.builder() - .title(title) - .topic(topic) - .description(description) - .build()); - } catch (RestClientResponseException ex) { - log.error("Content moderation webhook returned error status={} body={}", - ex.getStatusCode(), ex.getResponseBodyAsString(), ex); - throw new StoneInscriptionException( - "Content moderation webhook failed: " + ex.getStatusCode() + " " - + safeErrorMessage(ex.getResponseBodyAsString()), - HttpStatus.SERVICE_UNAVAILABLE); - } catch (RestClientException ex) { - log.error("Content moderation webhook call failed: {}", ex.getMessage(), ex); - throw new StoneInscriptionException( - "Content moderation service is unavailable. Your content was not saved. Cause: " - + safeErrorMessage(ex.getMessage()), - HttpStatus.SERVICE_UNAVAILABLE); - } - - List response = parseResponse(rawResponse); - - if (response == null || response.isEmpty() || response.get(0) == null) { - log.error("Content moderation webhook returned empty or invalid response"); - throw new StoneInscriptionException( - "Content moderation service returned an invalid response. Your content was not saved.", - HttpStatus.SERVICE_UNAVAILABLE); - } - - ContentModerationResponseDto moderationResponse = response.get(0); - Double confidence = moderationResponse.getConfidence() == null ? 0.0 : moderationResponse.getConfidence(); - String decision = normalize(moderationResponse.getDecision()); - boolean approved = "ALLOW".equals(decision) && confidence >= properties.getSafeThreshold(); - - return ContentModerationResult.builder() - .approved(approved) - .label(normalize(moderationResponse.getLabel())) - .confidence(confidence) - .decision(decision) - .status(normalize(moderationResponse.getStatus())) - .reason(cleanReason(moderationResponse.getReason())) - .build(); - } - - public String buildRejectionMessage(ContentModerationResult moderationResult) { - String reason = moderationResult.getReason(); - if (reason == null || reason.isBlank()) { - return "Content failed moderation and was not saved."; - } - - return "Content failed moderation and was not saved: " + reason; - } - - private void validateRequest(String title, String topic, String description) { - if (topic == null || topic.isBlank()) { - throw new StoneInscriptionException("Topic is required for content moderation.", HttpStatus.BAD_REQUEST); - } - - if (description == null || description.isBlank()) { - throw new StoneInscriptionException("Description is required for content moderation.", HttpStatus.BAD_REQUEST); - } - - if (title == null) { - throw new StoneInscriptionException("Title is required for content moderation.", HttpStatus.BAD_REQUEST); - } - } - - private String normalize(String value) { - if (value == null) { - return null; - } - - return value.trim().toUpperCase(Locale.ROOT); - } - - private String cleanReason(String reason) { - if (reason == null) { - return null; - } - - return reason.trim(); - } - - private List parseResponse(String rawResponse) { - if (rawResponse == null || rawResponse.isBlank()) { - log.error("Content moderation webhook returned blank response"); - throw new StoneInscriptionException( - "Content moderation service returned an invalid response. Your content was not saved.", - HttpStatus.SERVICE_UNAVAILABLE); - } - - try { - JsonNode root = objectMapper.readTree(rawResponse); - log.info("Content moderation raw response: {}", rawResponse); - - if (root.isArray()) { - return objectMapper.readValue(rawResponse, new TypeReference>() { - }); - } - - if (root.isObject()) { - return List.of(objectMapper.treeToValue(root, ContentModerationResponseDto.class)); - } - } catch (IOException ex) { - log.error("Failed to parse content moderation response body={}", rawResponse, ex); - throw new StoneInscriptionException( - "Content moderation service returned an unreadable response. Your content was not saved.", - HttpStatus.SERVICE_UNAVAILABLE); - } - - log.error("Unsupported content moderation response body={}", rawResponse); - throw new StoneInscriptionException( - "Content moderation service returned an unsupported response. Your content was not saved.", - HttpStatus.SERVICE_UNAVAILABLE); - } - - private String safeErrorMessage(String message) { - if (message == null || message.isBlank()) { - return "unknown error"; - } - - return message.replaceAll("\\s+", " ").trim(); - } -} 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 83b8e55..c2fac30 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,8 +31,6 @@ @RequestMapping("/post") public class PostController { - private static final int MAX_IMAGES_PER_POST = 16; - @Autowired private PostService postService; @@ -47,7 +45,7 @@ public class PostController { public ResponseEntity addPostWithFile( @RequestPart(value = "post", required = false) InscriptionPostDto InscriptionPostDto, HttpServletRequest request, - @RequestPart("files") MultipartFile... files) throws IOException { + @RequestPart("files") MultipartFile... files) throws IOException { files = getNonEmptyFiles(files); if (files.length == 0) { @@ -74,7 +72,7 @@ public ResponseEntity getAllPost() { } @GetMapping("/public/images/{id}") - public ResponseEntity getImage(@PathVariable("id") String id) { + public ResponseEntity getImage(@PathVariable String id) { return postService.getImages(id); @@ -196,9 +194,8 @@ public ResponseEntity descriptionDelete(HttpServletRequest request, String de return postService.descriptionDelete(jwtUtil.getUsernameFromToken(token), descriptionId); } - - // Updated this function for all the updation - // Helps logged user to create a post and upload images +// Updated this function for all the updation +// Helps logged user to create a post and upload images @PostMapping("/updatePost") @Secured("user") public ResponseEntity updatePost(HttpServletRequest request, @@ -326,67 +323,7 @@ public ResponseEntity deleteImagesFromPost(HttpServletRequest request, // return postService.postDelete(email, postId); // } - // files = getNonEmptyFiles(files); - // validateFiles(files); - - // return postService.updatePost(email, InscriptionPostDto, postId, - // deletedImageIds, files); - // } - - // @PostMapping("/test/addPostWithFile/{email}") - // public ResponseEntity addPostWithFileForTest( - // @PathVariable("email") String email, - // @RequestPart(value = "post", required = false) InscriptionPostDto - // InscriptionPostDto, - // @RequestPart("files") MultipartFile... files) throws IOException { - - // files = getNonEmptyFiles(files); - - // if (files.length == 0) { - // throw new StoneInscriptionException("No File Uploaded", - // HttpStatus.BAD_REQUEST); - // } - - // validateFiles(files); - - // return postService.addPostWithFile(InscriptionPostDto, files, email); - // } - - // @PostMapping("/test/addImagesToPost/{email}") - // public ResponseEntity addImagesToPostForTest( - // @PathVariable String email, - // @RequestParam String postId, - // @RequestPart("files") MultipartFile... files) { - - // files = getNonEmptyFiles(files); - - // if (files.length == 0) { - // throw new StoneInscriptionException("No File Uploaded", - // HttpStatus.BAD_REQUEST); - // } - - // validateFiles(files); - - // return postService.addImagesToPost(email, postId, files); - // } - - // @PostMapping("/test/deleteImagesFromPost/{email}") - // public ResponseEntity deleteImagesFromPostForTest( - // @PathVariable String email, - // @RequestParam String postId, - // @RequestParam(value = "deletedImageIds") List deletedImageIds) { - - // return postService.deleteImagesFromPost(email, postId, deletedImageIds); - // } - - // @PostMapping("/test/postDelete/{email}") - // public ResponseEntity postDeleteForTest( - // @PathVariable String email, - // @RequestParam String postId) { - - // return postService.postDelete(email, postId); - // } - +// end of test api's private MultipartFile[] getNonEmptyFiles(MultipartFile[] files) { if (files == null) { return new MultipartFile[0]; @@ -410,7 +347,7 @@ private void validateFiles(MultipartFile[] files) { } @PostMapping("/getCommentByUser") - @Secured("user") + // @Secured("user") public ResponseEntity getCommentByUser(HttpServletRequest request) { String token = request.getHeader("Authorization"); diff --git a/src/main/java/com/cadac/stone_inscription/post/dto/PublicPostUserDescriptionDto.java b/src/main/java/com/cadac/stone_inscription/post/dto/PublicPostUserDescriptionDto.java index 0c693a5..40068df 100644 --- a/src/main/java/com/cadac/stone_inscription/post/dto/PublicPostUserDescriptionDto.java +++ b/src/main/java/com/cadac/stone_inscription/post/dto/PublicPostUserDescriptionDto.java @@ -1,8 +1,7 @@ -package com.cadac.stone_inscription.post.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.cadac.stone_inscription.moderation.model.ContentModeration; -import lombok.*; +package com.cadac.stone_inscription.post.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; import java.util.Date; import java.util.List; @@ -34,14 +33,11 @@ public class PublicPostUserDescriptionDto { @JsonProperty("description") private String description; - @JsonProperty("upvote") - private Integer upvote; - - @JsonProperty("moderation") - private ContentModeration moderation; - - @JsonProperty("userVote") - private List userVote; + @JsonProperty("upvote") + private Integer upvote; + + @JsonProperty("userVote") + private List userVote; @JsonProperty("createdAt") private Date createdAt; diff --git a/src/main/java/com/cadac/stone_inscription/post/mapper/PublicPostDescriptionMapper.java b/src/main/java/com/cadac/stone_inscription/post/mapper/PublicPostDescriptionMapper.java index ea09e9c..157c17f 100644 --- a/src/main/java/com/cadac/stone_inscription/post/mapper/PublicPostDescriptionMapper.java +++ b/src/main/java/com/cadac/stone_inscription/post/mapper/PublicPostDescriptionMapper.java @@ -19,15 +19,14 @@ public static PublicPostUserDescriptionDto toDto(PublicPostDescription entity) { .postId(entity.getPostId() != null ? entity.getPostId().toString() : null) .userId(entity.getUserId() != null ? entity.getUserId().toString() : null) .username(entity.getUsername()) - .userImageUrl(entity.getUserImageUrl()) - .description(entity.getDescription()) - .upvote(entity.getUpvote()) - .moderation(entity.getModeration()) - .userVote(toUserVoteDtoList(entity.getUserVote())) - .createdAt(entity.getCreatedAt()) - .updatedAt(entity.getUpdatedAt()) - .build(); - } + .userImageUrl(entity.getUserImageUrl()) + .description(entity.getDescription()) + .upvote(entity.getUpvote()) + .userVote(toUserVoteDtoList(entity.getUserVote())) + .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) + .build(); + } private static List toUserVoteDtoList(List userVotes) { if (userVotes == null) { 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 a375380..868a912 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 @@ -27,8 +27,6 @@ import com.cadac.stone_inscription.entity.PublicPostDescription; import com.cadac.stone_inscription.entity.User; import com.cadac.stone_inscription.exception.StoneInscriptionException; -import com.cadac.stone_inscription.moderation.model.ContentModerationResult; -import com.cadac.stone_inscription.moderation.service.ContentModerationService; import com.cadac.stone_inscription.post.dto.InscriptionPostDto; import com.cadac.stone_inscription.post.dto.PublicPostUserDescriptionDto; import com.cadac.stone_inscription.post.mapper.PostMapper; @@ -44,8 +42,6 @@ @Service public class PostServiceImp implements PostService { - private static final int MAX_IMAGES_PER_POST = 16; - @Autowired private UserRepository userRepository; @@ -61,24 +57,16 @@ public class PostServiceImp implements PostService { @Autowired private PublicPostDescriptionRepo publicPostDescriptionRepo; - @Autowired - private ContentModerationService contentModerationService; - @Value("${app.backend.url}") private String backendUrl; - // Create Post + Process Images + Extract Location + Save Post + Update User - // Stats - +// Create Post + Process Images + Extract Location + Save Post + Update User Stats + @Override public ResponseEntity addPostWithFile(InscriptionPostDto inscriptionPostDto, MultipartFile[] files, String usernameFromToken) { - ensureMaximumImageCount(0, 0, files.length); - - User user = userRepository.findByEmail(usernameFromToken); - List ls = validateAndExtractImages(files, user.getId(), Collections.emptySet(), true); - ensureMaximumImageCount(0, 0, ls.size()); + List ls = validateAndExtractImages(files, Collections.emptySet(), true); // Below Line To use for Threshold similarty @@ -100,9 +88,9 @@ public ResponseEntity addPostWithFile(InscriptionPostDto inscriptionPostDto, // .filter(Objects::nonNull) // .findFirst(); - ContentModerationResult moderationResult = moderatePostContent(inscriptionPostDto); + User user = userRepository.findByEmail(usernameFromToken); - user.setImagesUploaded(user.getImagesUploaded() + ls.size()); + adjustUserImagesUploaded(user, ls.size()); userRepository.save(user); ObjectId postUserId = user.getId(); @@ -113,12 +101,6 @@ public ResponseEntity addPostWithFile(InscriptionPostDto inscriptionPostDto, inscriptionPost = PostMapper.toEntity(inscriptionPostDto); } - if (inscriptionPost.getDescription() == null) { - inscriptionPost.setDescription(InscriptionPost.Description.builder().build()); - } - - inscriptionPost.getDescription().setModeration(moderationResult.toContentModeration()); - inscriptionPost.setUserId(postUserId); if (geoLocationCordinates.isPresent()) { @@ -172,7 +154,7 @@ public ResponseEntity addPostWithFile(InscriptionPostDto inscriptionPostDto, } inscriptionPostRepo.save(inscriptionPost); - return UserResponse.responseHandler("Post saved successfully after content moderation.", HttpStatus.OK, true); + return UserResponse.responseHandler("Images Uploaded Sucessfully", HttpStatus.OK, true); } @Override @@ -230,21 +212,15 @@ public ResponseEntity addPoastDiscription(String usernameFromToken, String po User user = userRepository.findByEmail(usernameFromToken); - Optional post = inscriptionPostRepo.findById(new ObjectId(postId)); - - if (post.isEmpty()) { + if (inscriptionPostRepo.findById(new ObjectId(postId)).isEmpty()) { throw new StoneInscriptionException("Unprocesable request", HttpStatus.BAD_REQUEST); } - ContentModerationResult moderationResult = moderateCommentContent(post.get(), discription); - publicPostDescriptionRepo.save(PublicPostDescription.builder().description(discription) .postId(new ObjectId(postId)).userId(user.getId()) - .userImageUrl(user.getProfileImage()).username(user.getName()) - .moderation(moderationResult.toContentModeration()).build()); + .userImageUrl(user.getProfileImage()).username(user.getName()).build()); - return UserResponse.responseHandler("Description saved successfully after content moderation.", HttpStatus.OK, - true); + return UserResponse.responseHandler("Discription Added sucessfully", HttpStatus.OK, true); } @Override @@ -267,18 +243,11 @@ public ResponseEntity updatePostDiscription(String usernameFromToken, String throw new StoneInscriptionException("Unprocesable request Unauthorized", HttpStatus.UNAUTHORIZED); } - InscriptionPost parentPost = inscriptionPostRepo.findById(postDiscription.get().getPostId()) - .orElseThrow(() -> new StoneInscriptionException("Parent post not found", HttpStatus.BAD_REQUEST)); - - ContentModerationResult moderationResult = moderateCommentContent(parentPost, discription); - postDiscription.get().setDescription(discription); - postDiscription.get().setModeration(moderationResult.toContentModeration()); publicPostDescriptionRepo.save(postDiscription.get()); - return UserResponse.responseHandler("Description updated successfully after content moderation.", HttpStatus.OK, - true); + return UserResponse.responseHandler("Updated Discription", HttpStatus.OK, true); } @@ -364,9 +333,10 @@ public ResponseEntity postDelete(String usernameFromToken, String postId) { if (!user.getId().toString().equals(postDelete.get().getUserId().toString())) { throw new StoneInscriptionException("Unprocesable request", HttpStatus.BAD_REQUEST); } - postDelete.get().getImages().getImage().stream().forEach(elem -> { - imagesDataRepo.deleteById(elem); - }); + int deletedImageCount = getExistingImageIds(postDelete.get()).size(); + getExistingImageIds(postDelete.get()).forEach(imagesDataRepo::deleteById); + adjustUserImagesUploaded(user, -deletedImageCount); + userRepository.save(user); publicPostDescriptionRepo.deleteAllByPostId(postId); inscriptionPostRepo.deleteById(new ObjectId(postId)); @@ -392,36 +362,30 @@ public ResponseEntity descriptionDelete(String usernameFromToken, String desc return UserResponse.responseHandler("description deleted", HttpStatus.OK, true); } - // Main function for the updation of the image - // edit post details - // delete some images - // upload new images - // keep at least one image in the post +// Main function for the updation of the image +// edit post details +// delete some images +// upload new images +// keep at least one image in the post @Override public ResponseEntity updatePost(String usernameFromToken, InscriptionPostDto inscriptionPostDto, String postId, List deletedImageIds, MultipartFile[] files) { InscriptionPost post = getOwnedPost(usernameFromToken, postId); + User user = userRepository.findByEmail(usernameFromToken); List existingImageIds = getExistingImageIds(post); List imagesToDelete = validateDeletedImageIds(existingImageIds, deletedImageIds, false); Set deletableImageIds = new HashSet<>(imagesToDelete); - ensureMaximumImageCount(existingImageIds.size(), deletableImageIds.size(), files == null ? 0 : files.length); - List newImages = validateAndExtractImages(files, post.getUserId(), deletableImageIds, false); + List newImages = validateAndExtractImages(files, deletableImageIds, false); ensureMinimumImageCount(existingImageIds.size(), deletableImageIds.size(), newImages.size()); - ensureMaximumImageCount(existingImageIds.size(), deletableImageIds.size(), newImages.size()); if (inscriptionPostDto != null) { post.setDescription(PostMapper.toEntityDescription(inscriptionPostDto.getDescription())); post.setScript(inscriptionPostDto.getScript()); post.setTopic(inscriptionPostDto.getTopic()); post.setType(inscriptionPostDto.getType()); - ContentModerationResult moderationResult = moderatePostContent(inscriptionPostDto); - if (post.getDescription() == null) { - post.setDescription(InscriptionPost.Description.builder().build()); - } - post.getDescription().setModeration(moderationResult.toContentModeration()); } List updatedImageIds = removeDeletedImageIds(existingImageIds, deletableImageIds); @@ -430,31 +394,32 @@ public ResponseEntity updatePost(String usernameFromToken, InscriptionPostDto updatePostImages(post, updatedImageIds, deletableImageIds); inscriptionPostRepo.save(post); deleteImagesByIds(deletableImageIds); - return UserResponse.responseHandler("Post updated successfully after content moderation.", HttpStatus.OK, true); + adjustUserImagesUploaded(user, newImages.size() - deletableImageIds.size()); + userRepository.save(user); + return UserResponse.responseHandler("post Updated ", HttpStatus.OK, true); } @Override public ResponseEntity addImagesToPost(String usernameFromToken, String postId, MultipartFile[] files) { InscriptionPost post = getOwnedPost(usernameFromToken, postId); User user = userRepository.findByEmail(usernameFromToken); - ensureMaximumImageCount(getExistingImageIds(post).size(), 0, files.length); - List newImages = validateAndExtractImages(files, user.getId(), Collections.emptySet(), true); - ensureMaximumImageCount(getExistingImageIds(post).size(), 0, newImages.size()); + List newImages = validateAndExtractImages(files, Collections.emptySet(), true); List updatedImageIds = getExistingImageIds(post); updatedImageIds.addAll(saveImages(post.getId(), newImages)); updatePostImages(post, updatedImageIds, Collections.emptySet()); inscriptionPostRepo.save(post); - updateUserImagesUploaded(usernameFromToken, newImages.size()); + adjustUserImagesUploaded(user, newImages.size()); + userRepository.save(user); return UserResponse.responseHandler("Images Added To Post Successfully", HttpStatus.OK, true); } @Override - public ResponseEntity deleteImagesFromPost(String usernameFromToken, String postId, - List deletedImageIds) { + public ResponseEntity deleteImagesFromPost(String usernameFromToken, String postId, List deletedImageIds) { InscriptionPost post = getOwnedPost(usernameFromToken, postId); + User user = userRepository.findByEmail(usernameFromToken); List existingImageIds = getExistingImageIds(post); List imagesToDelete = validateDeletedImageIds(existingImageIds, deletedImageIds, true); Set deletableImageIds = new HashSet<>(imagesToDelete); @@ -466,7 +431,8 @@ public ResponseEntity deleteImagesFromPost(String usernameFromToken, String p inscriptionPostRepo.save(post); deleteImagesByIds(deletableImageIds); - updateUserImagesUploaded(usernameFromToken, -deletableImageIds.size()); + adjustUserImagesUploaded(user, -deletableImageIds.size()); + userRepository.save(user); return UserResponse.responseHandler("Images Deleted From Post Successfully", HttpStatus.OK, true); } @@ -500,17 +466,25 @@ public ResponseEntity getDashboardCounts() { List allPost = inscriptionPostRepo.findAll(); - counts.put("totalUsers", userRepository.findAll().size()); - counts.put("totalPosts", allPost.size()); + counts.put("totalUsers", Math.toIntExact(userRepository.count())); + counts.put("totalPosts", Math.toIntExact(inscriptionPostRepo.count())); + counts.put("totalImages", Math.toIntExact(imagesDataRepo.count())); counts.put("totalGeoTaggedPosts", (int) allPost.stream().filter(el -> el.getDescription().getGeolocation() != null).count()); - counts.put("totalTranslations", 0); + counts.put("totalTranslations", (int) allPost.stream() + .map(InscriptionPost::getDescription) + .filter(Objects::nonNull) + .map(InscriptionPost.Description::getEnglishTranslation) + .filter(Objects::nonNull) + .map(String::trim) + .filter(translation -> !translation.isEmpty()) + .count()); return UserResponse.responseHandler("Dashboard Counts", HttpStatus.OK, counts); } - private List validateAndExtractImages(MultipartFile[] files, ObjectId userId, - Set replaceableImageIds, boolean filesRequired) { + private List validateAndExtractImages(MultipartFile[] files, Set replaceableImageIds, + boolean filesRequired) { if (files == null || files.length == 0) { if (filesRequired) { throw new StoneInscriptionException("No File Uploaded", HttpStatus.BAD_REQUEST); @@ -524,38 +498,24 @@ private List validateAndExtractImages(MultipartFile[] files, O if (ls.size() == 0) { throw new StoneInscriptionException("No Valid Image Found in the Request", HttpStatus.BAD_REQUEST); } - // here is the logic where we find the count of image in databse if count is - // more than 1 then it is duplicate image by same user + if (ls.size() != ls.stream().map(ImageMetaAndInfo::getPHash).distinct().count()) { throw new StoneInscriptionException("Duplicate Image Uploaded", HttpStatus.BAD_REQUEST); } - boolean imageAlreadyExists = ls.stream() - .anyMatch(image -> isImageAlreadyUploadedByUser(userId, - image.getPHash().getHashValue().toString(), replaceableImageIds)); + boolean imageAlreadyExists = ls.stream().anyMatch(image -> { + Optional existingImage = imagesDataRepo + .findFirstByMetadata_ImageHashValue(image.getPHash().getHashValue().toString()); + return existingImage.isPresent() && !replaceableImageIds.contains(existingImage.get().getId()); + }); if (imageAlreadyExists) { - throw new StoneInscriptionException("Image Already Uploaded", HttpStatus.CONFLICT); + throw new StoneInscriptionException("Image Already Uploaded By some User", HttpStatus.CONFLICT); } return ls; } - private boolean isImageAlreadyUploadedByUser(ObjectId userId, String imageHashValue, - Set replaceableImageIds) { - if (userId == null) { - return false; - } - - return imagesDataRepo.findAllByMetadata_ImageHashValue(imageHashValue).stream() - .filter(existingImage -> !replaceableImageIds.contains(existingImage.getId())) - .map(ImagesData::getPostId) - .filter(Objects::nonNull) - .map(inscriptionPostRepo::findById) - .flatMap(Optional::stream) - .anyMatch(post -> userId.equals(post.getUserId())); - } - private List saveImages(ObjectId postId, List images) { return images.stream().map(image -> imagesDataRepo.save(ImagesData.builder() .imageData(image.getFile()) @@ -614,16 +574,6 @@ private void ensureMinimumImageCount(int existingImageCount, int deletedImageCou throw new StoneInscriptionException("Post should have at least one image", HttpStatus.BAD_REQUEST); } } -// ensuring the max image should be only 16 - - private void ensureMaximumImageCount(int existingImageCount, int deletedImageCount, int newImageCount) { - int finalImageCount = existingImageCount - deletedImageCount + newImageCount; - - if (finalImageCount > MAX_IMAGES_PER_POST) { - throw new StoneInscriptionException("A post can contain at most " + MAX_IMAGES_PER_POST + " images", - HttpStatus.BAD_REQUEST); - } - } private List removeDeletedImageIds(List existingImageIds, Set deletableImageIds) { return existingImageIds.stream() @@ -648,23 +598,9 @@ private void deleteImagesByIds(Set imageIds) { imageIds.forEach(imagesDataRepo::deleteById); } - private void updateUserImagesUploaded(String usernameFromToken, int delta) { - if (delta == 0) { - return; - } - - User user = userRepository.findByEmail(usernameFromToken); - updateUserImagesUploaded(user, delta); - } - - private void updateUserImagesUploaded(User user, int delta) { - if (user == null || delta == 0) { - return; - } - - int currentImagesUploaded = user.getImagesUploaded() == null ? 0 : user.getImagesUploaded(); - user.setImagesUploaded(Math.max(0, currentImagesUploaded + delta)); - userRepository.save(user); + private void adjustUserImagesUploaded(User user, int delta) { + int currentCount = user.getImagesUploaded() == null ? 0 : user.getImagesUploaded(); + user.setImagesUploaded(Math.max(0, currentCount + delta)); } private List sanitizeImageIds(List imageIds) { @@ -680,53 +616,4 @@ private List sanitizeImageIds(List imageIds) { .toList(); } - private ContentModerationResult moderatePostContent(InscriptionPostDto inscriptionPostDto) { - if (inscriptionPostDto == null || inscriptionPostDto.getDescription() == null) { - throw new StoneInscriptionException("Post content is required for moderation.", HttpStatus.BAD_REQUEST); - } - - String title = safeValue(inscriptionPostDto.getDescription().getTitle()); - String topic = requiredValue(inscriptionPostDto.getTopic(), "Topic is required for content moderation."); - String description = requiredValue(inscriptionPostDto.getDescription().getDescription(), - "Description is required for content moderation."); - - ContentModerationResult moderationResult = contentModerationService.moderate(title, topic, description); - ensureModerationApproved(moderationResult); - return moderationResult; - } - - private ContentModerationResult moderateCommentContent(InscriptionPost post, String description) { - String title = ""; - if (post.getDescription() != null) { - title = safeValue(post.getDescription().getTitle()); - } - - String topic = requiredValue(post.getTopic(), "Topic is required for content moderation."); - String moderationDescription = requiredValue(description, "Description is required for content moderation."); - - ContentModerationResult moderationResult = contentModerationService.moderate(title, topic, - moderationDescription); - ensureModerationApproved(moderationResult); - return moderationResult; - } - - private void ensureModerationApproved(ContentModerationResult moderationResult) { - if (!moderationResult.isApproved()) { - throw new StoneInscriptionException(contentModerationService.buildRejectionMessage(moderationResult), - HttpStatus.UNPROCESSABLE_ENTITY); - } - } - - private String requiredValue(String value, String message) { - if (value == null || value.isBlank()) { - throw new StoneInscriptionException(message, HttpStatus.BAD_REQUEST); - } - - return value.trim(); - } - - private String safeValue(String value) { - return value == null ? "" : value.trim(); - } - } diff --git a/src/main/java/com/cadac/stone_inscription/repository/ImagesDataRepo.java b/src/main/java/com/cadac/stone_inscription/repository/ImagesDataRepo.java index 0b876d9..8aa9a1f 100644 --- a/src/main/java/com/cadac/stone_inscription/repository/ImagesDataRepo.java +++ b/src/main/java/com/cadac/stone_inscription/repository/ImagesDataRepo.java @@ -1,6 +1,5 @@ package com.cadac.stone_inscription.repository; -import java.util.List; import java.util.Optional; import org.springframework.data.mongodb.repository.MongoRepository; @@ -8,11 +7,11 @@ import com.cadac.stone_inscription.entity.ImagesData; +import dev.brachtendorf.jimagehash.hash.Hash; + @Repository public interface ImagesDataRepo extends MongoRepository { - Optional findFirstByMetadata_ImageHashValue(String imageHashValue); - - List findAllByMetadata_ImageHashValue(String imageHashValue); - + Optional findFirstByMetadata_ImageHashValue(String imageHashValue); + } diff --git a/src/main/java/com/cadac/stone_inscription/user/controller/UserController.java b/src/main/java/com/cadac/stone_inscription/user/controller/UserController.java index 8e5acda..92d9abb 100644 --- a/src/main/java/com/cadac/stone_inscription/user/controller/UserController.java +++ b/src/main/java/com/cadac/stone_inscription/user/controller/UserController.java @@ -100,54 +100,48 @@ public ResponseEntity getUserImage(@PathVariable String id) // TODO: Remove these methods when testing is done to restore normal behavior. // ============================ - /** - * TEST ONLY: Get profile directly by email without JWT. - * GET /api/v1/user/test/profile/{email} - */ + // /** + // * TEST ONLY: Get profile directly by email without JWT. + // * GET /api/v1/user/test/profile/{email} + // */ // @GetMapping("/test/profile/{email}") // public ResponseEntity getProfileForTest(@PathVariable String email) { // return userService.getProfile(email); // } - /** - * TEST ONLY: Update profile directly by email without JWT. - * POST /api/v1/user/test/updateProfile/{email} - */ + // /** + // * TEST ONLY: Update profile directly by email without JWT. + // * POST /api/v1/user/test/updateProfile/{email} + // */ // @PostMapping("/test/updateProfile/{email}") // public ResponseEntity updateProfileForTest( // @PathVariable String email, // @Valid @RequestBody UpdateProfileRequest updateProfileRequest) { - // return userService.updateProfile(email, updateProfileRequest); - // } // return userService.updateProfile(email, updateProfileRequest); // } - /** - * TEST ONLY: Upload profile image directly by email without JWT. - * POST /api/v1/user/test/uploadProfileImage/{email} - */ + // /** + // * TEST ONLY: Upload profile image directly by email without JWT. + // * POST /api/v1/user/test/uploadProfileImage/{email} + // */ // @PostMapping("/test/uploadProfileImage/{email}") // public ResponseEntity uploadProfileImageForTest( // @PathVariable String email, // @RequestPart("file") MultipartFile file) { - // return userService.uploadProfileImage(email, file); - // } // return userService.uploadProfileImage(email, file); // } - /** - * TEST ONLY: Upload cover image directly by email without JWT. - * POST /api/v1/user/test/uploadCoverImage/{email} - */ + // /** + // * TEST ONLY: Upload cover image directly by email without JWT. + // * POST /api/v1/user/test/uploadCoverImage/{email} + // */ // @PostMapping("/test/uploadCoverImage/{email}") // public ResponseEntity uploadCoverImageForTest( // @PathVariable String email, // @RequestPart("file") MultipartFile file) { - // return userService.uploadCoverImage(email, file); - // } // return userService.uploadCoverImage(email, file); // } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 81179fa..4715147 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,12 +16,7 @@ app.cors.url=https://inscriptions.cdacb.in app.backend.url= https://inscriptions.cdacb.in/api -geolocation.api.url=https://nominatim.openstreetmap.org/reverse -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} +geolocation.api.url=https://nominatim.openstreetmap.org/reverse # JWT configuration @@ -64,7 +59,7 @@ 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 +spring.servlet.multipart.max-request-size=75MB management.endpoints.web.exposure.include=health,info,prometheus management.endpoint.prometheus.enabled=true diff --git a/src/test/java/com/cadac/stone_inscription/moderation/service/ContentModerationServiceTest.java b/src/test/java/com/cadac/stone_inscription/moderation/service/ContentModerationServiceTest.java deleted file mode 100644 index 3580a97..0000000 --- a/src/test/java/com/cadac/stone_inscription/moderation/service/ContentModerationServiceTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.cadac.stone_inscription.moderation.service; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import static org.mockito.Mockito.when; -import org.mockito.MockitoAnnotations; -import org.springframework.http.HttpStatus; -import org.springframework.web.client.RestClientException; - -import com.cadac.stone_inscription.exception.StoneInscriptionException; -import com.cadac.stone_inscription.moderation.client.N8nModerationClient; -import com.cadac.stone_inscription.moderation.config.ContentModerationProperties; -import com.cadac.stone_inscription.moderation.model.ContentModerationResult; - -class ContentModerationServiceTest { - - @Mock - private N8nModerationClient n8nModerationClient; - - private ContentModerationService contentModerationService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - ContentModerationProperties properties = new ContentModerationProperties(); - properties.setSafeThreshold(0.7); - properties.setWebhookUrl("https://example.com/moderation"); - contentModerationService = new ContentModerationService(n8nModerationClient, properties); - } - - @Test - void shouldApproveContentWhenDecisionIsAllowAndConfidenceMeetsThreshold() { - String jsonResponse = "[{\"decision\":\"ALLOW\",\"label\":\"SAFE\",\"confidence\":0.92,\"status\":\"approved\",\"reason\":\"safe\"}]"; - when(n8nModerationClient.moderate(org.mockito.ArgumentMatchers.any())) - .thenReturn(jsonResponse); - - ContentModerationResult result = contentModerationService.moderate("Title", "History", "Safe content"); - - assertTrue(result.isApproved()); - assertEquals("SAFE", result.getLabel()); - assertEquals("ALLOW", result.getDecision()); - } - - @Test - void shouldRejectContentWhenConfidenceIsBelowThreshold() { - String jsonResponse = "[{\"decision\":\"ALLOW\",\"label\":\"SAFE\",\"confidence\":0.6,\"status\":\"approved\",\"reason\":\"low confidence\"}]"; - when(n8nModerationClient.moderate(org.mockito.ArgumentMatchers.any())) - .thenReturn(jsonResponse); - - ContentModerationResult result = contentModerationService.moderate("Title", "History", "Borderline content"); - - assertFalse(result.isApproved()); - assertEquals("SAFE", result.getLabel()); - } - - @Test - void shouldFailClosedWhenWebhookCallFails() { - when(n8nModerationClient.moderate(org.mockito.ArgumentMatchers.any())) - .thenThrow(new RestClientException("timeout")); - - StoneInscriptionException exception = assertThrows(StoneInscriptionException.class, - () -> contentModerationService.moderate("Title", "History", "Safe content")); - - assertEquals(HttpStatus.SERVICE_UNAVAILABLE, exception.getHttpStatus()); - } -}