From b923b3a69aa44aebbb7696da1405975e40cd6975 Mon Sep 17 00:00:00 2001 From: ruksanasemeir <ruksana.semeir@tarento.com> Date: Tue, 6 Feb 2024 15:58:55 +0530 Subject: [PATCH] Comment hub first commit: Initial set up --- pom.xml | 103 +++++++ .../commenthub/CommentHubApplication.java | 13 + .../commenthub/config/RedisConfig.java | 24 ++ .../commenthub/constant/Constants.java | 61 ++++ .../controller/CommentController.java | 111 ++++++++ .../dto/CommentTreeIdentifierDTO.java | 19 ++ .../commenthub/dto/CommentsResoponseDTO.java | 22 ++ .../MultipleWorkflowsCommentResponseDTO.java | 20 ++ .../tarento/commenthub/dto/ResponseDTO.java | 19 ++ .../tarento/commenthub/entity/Comment.java | 44 +++ .../commenthub/entity/CommentTree.java | 42 +++ .../exception/CommentException.java | 36 +++ .../commenthub/exception/ErrorResponse.java | 15 + .../exception/RestExceptionHandling.java | 40 +++ .../repository/CommentRepository.java | 22 ++ .../repository/CommentTreeRepository.java | 28 ++ .../commenthub/service/CommentService.java | 29 ++ .../service/CommentTreeService.java | 26 ++ .../service/impl/CommentServiceImpl.java | 269 ++++++++++++++++++ .../service/impl/CommentTreeServiceImpl.java | 265 +++++++++++++++++ .../commenthub/utility/CommentsUtility.java | 20 ++ .../tarento/commenthub/utility/Status.java | 6 + src/main/resources/application.properties | 32 +++ .../payloadValidation/firstComment.json | 63 ++++ .../payloadValidation/newComment.json | 53 ++++ .../payloadValidation/updateComment.json | 54 ++++ .../CommentHubApplicationTests.java | 13 + 27 files changed, 1449 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/tarento/commenthub/CommentHubApplication.java create mode 100644 src/main/java/com/tarento/commenthub/config/RedisConfig.java create mode 100644 src/main/java/com/tarento/commenthub/constant/Constants.java create mode 100644 src/main/java/com/tarento/commenthub/controller/CommentController.java create mode 100644 src/main/java/com/tarento/commenthub/dto/CommentTreeIdentifierDTO.java create mode 100644 src/main/java/com/tarento/commenthub/dto/CommentsResoponseDTO.java create mode 100644 src/main/java/com/tarento/commenthub/dto/MultipleWorkflowsCommentResponseDTO.java create mode 100644 src/main/java/com/tarento/commenthub/dto/ResponseDTO.java create mode 100644 src/main/java/com/tarento/commenthub/entity/Comment.java create mode 100644 src/main/java/com/tarento/commenthub/entity/CommentTree.java create mode 100644 src/main/java/com/tarento/commenthub/exception/CommentException.java create mode 100644 src/main/java/com/tarento/commenthub/exception/ErrorResponse.java create mode 100644 src/main/java/com/tarento/commenthub/exception/RestExceptionHandling.java create mode 100644 src/main/java/com/tarento/commenthub/repository/CommentRepository.java create mode 100644 src/main/java/com/tarento/commenthub/repository/CommentTreeRepository.java create mode 100644 src/main/java/com/tarento/commenthub/service/CommentService.java create mode 100644 src/main/java/com/tarento/commenthub/service/CommentTreeService.java create mode 100644 src/main/java/com/tarento/commenthub/service/impl/CommentServiceImpl.java create mode 100644 src/main/java/com/tarento/commenthub/service/impl/CommentTreeServiceImpl.java create mode 100644 src/main/java/com/tarento/commenthub/utility/CommentsUtility.java create mode 100644 src/main/java/com/tarento/commenthub/utility/Status.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/payloadValidation/firstComment.json create mode 100644 src/main/resources/payloadValidation/newComment.json create mode 100644 src/main/resources/payloadValidation/updateComment.json create mode 100644 src/test/java/com/tarento/commenthub/CommentHubApplicationTests.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9b6f14f --- /dev/null +++ b/pom.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.7.15</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.tarento</groupId> + <artifactId>comment-hub</artifactId> + <version>0.0.1-SNAPSHOT</version> + <name>comment-hub</name> + <description>Demo project for Spring Boot</description> + <properties> + <java.version>11</java.version> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-redis</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.vladmihalcea</groupId> + <artifactId>hibernate-types-52</artifactId> + <version>2.3.4</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.6.12</version> + </dependency> + <dependency> + <groupId>com.auth0</groupId> + <artifactId>java-jwt</artifactId> + <version>4.4.0</version> + </dependency> + <dependency> + <groupId>com.fasterxml.uuid</groupId> + <artifactId>java-uuid-generator</artifactId> + <version>4.2.0</version> + </dependency> + <dependency> + <groupId>com.networknt</groupId> + <artifactId>json-schema-validator</artifactId> + <version>1.0.86</version> + <exclusions> + <exclusion> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <excludes> + <exclude> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </exclude> + </excludes> + </configuration> + </plugin> + </plugins> + <finalName>kubernetes</finalName> + </build> + +</project> diff --git a/src/main/java/com/tarento/commenthub/CommentHubApplication.java b/src/main/java/com/tarento/commenthub/CommentHubApplication.java new file mode 100644 index 0000000..5bae5d8 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/CommentHubApplication.java @@ -0,0 +1,13 @@ +package com.tarento.commenthub; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CommentHubApplication { + + public static void main(String[] args) { + SpringApplication.run(CommentHubApplication.class, args); + } + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/config/RedisConfig.java b/src/main/java/com/tarento/commenthub/config/RedisConfig.java new file mode 100644 index 0000000..19cf134 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/config/RedisConfig.java @@ -0,0 +1,24 @@ + +package com.tarento.commenthub.config; + + +import com.tarento.commenthub.entity.Comment; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate<String, Comment> redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate<String, Comment> redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + return redisTemplate; + } + +} + diff --git a/src/main/java/com/tarento/commenthub/constant/Constants.java b/src/main/java/com/tarento/commenthub/constant/Constants.java new file mode 100644 index 0000000..f21ab9b --- /dev/null +++ b/src/main/java/com/tarento/commenthub/constant/Constants.java @@ -0,0 +1,61 @@ +package com.tarento.commenthub.constant; + +public class Constants { + + private Constants() { + + } + + public static final String ENTITY_ID = "entityId"; + + public static final String ENTITY_TYPE = "entityType"; + + public static final String WORKFLOW = "workflow"; + + public static final String COMMENT_ID = "commentId"; + + public static final String COMMENT_TREE_ID = "commentTreeId"; + + public static final String COMMENT_KEY = "comment_"; + + public static final String COMMENTS = "comments"; + + public static final String CHILD_NODES = "childNodes"; + + public static final String ERROR = "ERROR"; + + public static final String HIERARCHY_PATH = "hierarchyPath"; + + public static final String COMMENT_DATA = "commentData"; + + public static final String COMMENT_TREE_DATA = "commentTreeData"; + + public static final String CHILDREN = "children"; + + public static final String FIRST_LEVEL_NODES = "firstLevelNodes"; + + public static final String COMMENT_SOURCE = "commentSource"; + public static final String FILE = "file"; + public static final String SUCCESS_STRING = "success"; + + public static final String DUPLICATE_TREE_ERROR = "DUPLICATE TREE CREATION ERROR"; + + public static final String DUPLICATE_TREE_ERROR_MESSAGE = + "Failed to create a new comment tree. " + + "A comment tree with the same 'entityType,' 'entityId,' and 'workflow' already exists."; + + public static final String WRONG_HIERARCHY_PATH_ERROR = "WRONG HIERARCHY PATH ERROR"; + public static final String ADD_FIRST_COMMENT_PAYLOAD_VALIDATION_FILE = "/payloadValidation/firstComment.json"; + public static final String ADD_NEW_COMMENT_PAYLOAD_VALIDATION_FILE = "/payloadValidation/newComment.json"; + public static final String UPDATE_EXISTING_COMMENT_VALIDATION_FILE = "/payloadValidation/updateComment.json"; + public static final String RESOLVED = "resolved"; + + public static final String COMMENT_RESOLVED = "commentResolved"; + + public static final String TRUE = "true"; + + public static final String FALSE = "false"; + + + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/controller/CommentController.java b/src/main/java/com/tarento/commenthub/controller/CommentController.java new file mode 100644 index 0000000..df979f0 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/controller/CommentController.java @@ -0,0 +1,111 @@ +package com.tarento.commenthub.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.tarento.commenthub.constant.Constants; +import com.tarento.commenthub.dto.CommentTreeIdentifierDTO; +import com.tarento.commenthub.dto.MultipleWorkflowsCommentResponseDTO; +import com.tarento.commenthub.dto.CommentsResoponseDTO; +import com.tarento.commenthub.dto.ResponseDTO; +import com.tarento.commenthub.entity.Comment; +import com.tarento.commenthub.entity.CommentTree; +import com.tarento.commenthub.service.CommentService; +import com.tarento.commenthub.service.CommentTreeService; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/comment") +@Slf4j +public class CommentController { + + @Autowired + CommentTreeService commentTreeService; + + @Autowired + private CommentService commentService; + + @PostMapping("/addFirst") + public ResponseDTO addFirstComment(@RequestBody JsonNode payload) { + return commentService.addFirstCommentToCreateTree(payload); + } + + @PostMapping("/addNew") + public ResponseDTO addNewComment(@RequestBody JsonNode payload) { + return commentService.addNewCommentToTree(payload); + } + + @PutMapping("/update") + public ResponseDTO updateExistingComment(@RequestBody JsonNode payload) { + return commentService.updateExistingComment(payload); + } + + @GetMapping("/getAll") + public CommentsResoponseDTO getComments( + @RequestParam(name = "entityType") String entityType, + @RequestParam(name = "entityId") String entityId, + @RequestParam(name = "workflow") String workflow) { + + CommentTreeIdentifierDTO commentTreeIdentifierDTO = new CommentTreeIdentifierDTO(); + commentTreeIdentifierDTO.setEntityType(entityType); + commentTreeIdentifierDTO.setEntityId(entityId); + commentTreeIdentifierDTO.setWorkflow(workflow); + + return commentService.getComments(commentTreeIdentifierDTO); + } + + @GetMapping("/multipleWorkflows") + public List<MultipleWorkflowsCommentResponseDTO> getCommentsForMultipleWorkflows( + @RequestParam(name = "entityType") String entityType, + @RequestParam(name = "entityId") String entityId, + @RequestParam(name = "workflow") List<String> workflows) { + return commentService.getComments(entityType, entityId, workflows); + } + + @DeleteMapping("/delete/{commentId}") + public Comment deleteComment( + @PathVariable String commentId, + @RequestParam(name = "entityType") String entityType, + @RequestParam(name = "entityId") String entityId, + @RequestParam(name = "workflow") String workflow) { + + CommentTreeIdentifierDTO commentTreeIdentifierDTO = new CommentTreeIdentifierDTO(); + commentTreeIdentifierDTO.setEntityType(entityType); + commentTreeIdentifierDTO.setEntityId(entityId); + commentTreeIdentifierDTO.setWorkflow(workflow); + + return commentService.deleteCommentById(commentId, commentTreeIdentifierDTO); + } + + @PostMapping("/setStatusToResolved") + public CommentTree setCommentTreeStatusToResolved( + @RequestParam(name = "entityType") String entityType, + @RequestParam(name = "entityId") String entityId, + @RequestParam(name = "workflow") String workflow) { + + CommentTreeIdentifierDTO commentTreeIdentifierDTO = new CommentTreeIdentifierDTO(); + commentTreeIdentifierDTO.setEntityType(entityType); + commentTreeIdentifierDTO.setEntityId(entityId); + commentTreeIdentifierDTO.setWorkflow(workflow); + return commentTreeService.setCommentTreeStatusToResolved(commentTreeIdentifierDTO); + } + + @PostMapping("/{commentId}/resolve") + public Comment resolveComment(@PathVariable String commentId) { + return commentService.resolveComment(commentId); + } + + @GetMapping("/health") + public String healthCheck() { + return Constants.SUCCESS_STRING; + } +} diff --git a/src/main/java/com/tarento/commenthub/dto/CommentTreeIdentifierDTO.java b/src/main/java/com/tarento/commenthub/dto/CommentTreeIdentifierDTO.java new file mode 100644 index 0000000..df645a4 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/dto/CommentTreeIdentifierDTO.java @@ -0,0 +1,19 @@ +package com.tarento.commenthub.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CommentTreeIdentifierDTO { + + private String entityType; + + private String entityId; + + private String workflow; +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/dto/CommentsResoponseDTO.java b/src/main/java/com/tarento/commenthub/dto/CommentsResoponseDTO.java new file mode 100644 index 0000000..93b21a6 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/dto/CommentsResoponseDTO.java @@ -0,0 +1,22 @@ +package com.tarento.commenthub.dto; + +import com.tarento.commenthub.entity.Comment; +import com.tarento.commenthub.entity.CommentTree; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CommentsResoponseDTO { + + private CommentTree commentTree; + + private List<Comment> comments; + + private int commentCount; +} diff --git a/src/main/java/com/tarento/commenthub/dto/MultipleWorkflowsCommentResponseDTO.java b/src/main/java/com/tarento/commenthub/dto/MultipleWorkflowsCommentResponseDTO.java new file mode 100644 index 0000000..0606cfe --- /dev/null +++ b/src/main/java/com/tarento/commenthub/dto/MultipleWorkflowsCommentResponseDTO.java @@ -0,0 +1,20 @@ +package com.tarento.commenthub.dto; + +import com.tarento.commenthub.entity.Comment; +import com.tarento.commenthub.entity.CommentTree; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class MultipleWorkflowsCommentResponseDTO { + + private CommentTree commentTree; + private List<Comment> comments; + private int commentCount; +} diff --git a/src/main/java/com/tarento/commenthub/dto/ResponseDTO.java b/src/main/java/com/tarento/commenthub/dto/ResponseDTO.java new file mode 100644 index 0000000..f2c4c2b --- /dev/null +++ b/src/main/java/com/tarento/commenthub/dto/ResponseDTO.java @@ -0,0 +1,19 @@ +package com.tarento.commenthub.dto; + +import com.tarento.commenthub.entity.Comment; +import com.tarento.commenthub.entity.CommentTree; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ResponseDTO { + + private CommentTree commentTree; + + private Comment comment; +} diff --git a/src/main/java/com/tarento/commenthub/entity/Comment.java b/src/main/java/com/tarento/commenthub/entity/Comment.java new file mode 100644 index 0000000..b97aa47 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/entity/Comment.java @@ -0,0 +1,44 @@ +package com.tarento.commenthub.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.UpdateTimestamp; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "comment") +@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +public class Comment implements Serializable { + + @Id + private String commentId; + + @Type(type = "jsonb") + @Column(columnDefinition = "jsonb") + private JsonNode commentData; + + @Column(columnDefinition = "varchar(255) default 'active'") + private String status; + + private Timestamp createdDate; + + private Timestamp lastUpdatedDate; + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/entity/CommentTree.java b/src/main/java/com/tarento/commenthub/entity/CommentTree.java new file mode 100644 index 0000000..d21810b --- /dev/null +++ b/src/main/java/com/tarento/commenthub/entity/CommentTree.java @@ -0,0 +1,42 @@ +package com.tarento.commenthub.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.UpdateTimestamp; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "comment_tree") +@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +public class CommentTree { + + @Id + private String commentTreeId; + + @Type(type = "jsonb") + @Column(columnDefinition = "jsonb") + private JsonNode commentTreeData; + + @Column(columnDefinition = "varchar(255) default 'active'") + private String status; + + private Timestamp createdDate; + + private Timestamp lastUpdatedDate; +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/exception/CommentException.java b/src/main/java/com/tarento/commenthub/exception/CommentException.java new file mode 100644 index 0000000..d416671 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/exception/CommentException.java @@ -0,0 +1,36 @@ +package com.tarento.commenthub.exception; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Getter +@Setter +@Component +public class CommentException extends RuntimeException{ + private String code; + private String message; + private String httpStatusCode; + private Map<String, String> errors; + + public CommentException() { + } + + public CommentException(String code, String message) { + this.code = code; + this.message = message; + } + + public CommentException(String code, String message, String httpStatusCode) { + this.code = code; + this.message = message; + this.httpStatusCode = httpStatusCode; + } + + public CommentException(Map<String, String> errors) { + this.message = errors.toString(); + this.errors = errors; + } +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/exception/ErrorResponse.java b/src/main/java/com/tarento/commenthub/exception/ErrorResponse.java new file mode 100644 index 0000000..1fd718d --- /dev/null +++ b/src/main/java/com/tarento/commenthub/exception/ErrorResponse.java @@ -0,0 +1,15 @@ +package com.tarento.commenthub.exception; + +import java.util.Map; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class ErrorResponse { + + private String code; + private String message; + private Map<String, String> errors; + private String httpStatusCode; +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/exception/RestExceptionHandling.java b/src/main/java/com/tarento/commenthub/exception/RestExceptionHandling.java new file mode 100644 index 0000000..297ac90 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/exception/RestExceptionHandling.java @@ -0,0 +1,40 @@ +package com.tarento.commenthub.exception; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Slf4j +public class RestExceptionHandling { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception ex) { + log.debug("RestExceptionHandler::handleException::" + ex); + HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; + ErrorResponse errorResponse = null; + if (ex instanceof CommentException) { + CommentException commentException = (CommentException) ex; + status = HttpStatus.BAD_REQUEST; + errorResponse = ErrorResponse.builder() + .code(commentException.getCode()) + .message(commentException.getMessage()) + .httpStatusCode(commentException.getHttpStatusCode() != null + ? commentException.getHttpStatusCode() + : String.valueOf(status.value())) + .build(); + if (StringUtils.isNotBlank(commentException.getMessage())) { + log.error(commentException.getMessage()); + } + + return new ResponseEntity<>(errorResponse, status); + } + errorResponse = ErrorResponse.builder() + .code(ex.getMessage()).build(); + return new ResponseEntity<>(errorResponse, status); + } + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/repository/CommentRepository.java b/src/main/java/com/tarento/commenthub/repository/CommentRepository.java new file mode 100644 index 0000000..ef6f10a --- /dev/null +++ b/src/main/java/com/tarento/commenthub/repository/CommentRepository.java @@ -0,0 +1,22 @@ +package com.tarento.commenthub.repository; + +import com.tarento.commenthub.entity.Comment; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface CommentRepository extends JpaRepository<Comment, String> { + + + Optional<Comment> findByCommentIdAndStatus(String id, String status); + + List<Comment> findByCommentIdInAndStatus(List<String> ids, String status); + + List<Comment> findByStatus(String status); + + @Query(value = "SELECT created_date FROM comment WHERE comment_id = ?1", nativeQuery = true) + LocalDateTime getCreatedDateByCommentId(String commentId); + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/repository/CommentTreeRepository.java b/src/main/java/com/tarento/commenthub/repository/CommentTreeRepository.java new file mode 100644 index 0000000..84dc805 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/repository/CommentTreeRepository.java @@ -0,0 +1,28 @@ +package com.tarento.commenthub.repository; + +import com.tarento.commenthub.entity.CommentTree; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CommentTreeRepository extends JpaRepository<CommentTree, String> { + + @Query(value = "SELECT * " + + "FROM comment_tree " + + "WHERE comment_tree_id IN (" + + " SELECT comment_tree_id " + + " FROM comment_tree " + + " WHERE (comment_tree_data->>'entityId' = :entityId AND comment_tree_data->>'entityType' = :entityType " + + " AND comment_tree_data->>'workflow' IN (:workflowList))" + + ")", nativeQuery = true) + List<CommentTree> getAllCommentTreeForMultipleWorkflows( + @Param("entityId") String entityId, + @Param("entityType") String entityType, + @Param("workflowList") List<String> workflowList + ); + + @Query(value = "SELECT COUNT(*) FROM comment_tree WHERE comment_tree_id = ?1", nativeQuery = true) + int getIdCount(String commentTreeId); +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/service/CommentService.java b/src/main/java/com/tarento/commenthub/service/CommentService.java new file mode 100644 index 0000000..6741943 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/service/CommentService.java @@ -0,0 +1,29 @@ +package com.tarento.commenthub.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.tarento.commenthub.dto.CommentTreeIdentifierDTO; +import com.tarento.commenthub.dto.MultipleWorkflowsCommentResponseDTO; +import com.tarento.commenthub.dto.CommentsResoponseDTO; +import com.tarento.commenthub.dto.ResponseDTO; +import com.tarento.commenthub.entity.Comment; +import io.swagger.v3.core.util.Json; +import java.util.List; + +public interface CommentService { + + ResponseDTO addFirstCommentToCreateTree(JsonNode payload); + + ResponseDTO addNewCommentToTree(JsonNode payload); + + ResponseDTO updateExistingComment(JsonNode paylaod); + + CommentsResoponseDTO getComments(CommentTreeIdentifierDTO commentTreeIdentifierDTO); + + List<MultipleWorkflowsCommentResponseDTO> getComments(String entityType, String entityId, + List<String> workflowList); + + Comment deleteCommentById(String commentId, CommentTreeIdentifierDTO commentTreeIdentifierDTO); + + Comment resolveComment(String commentId); + +} diff --git a/src/main/java/com/tarento/commenthub/service/CommentTreeService.java b/src/main/java/com/tarento/commenthub/service/CommentTreeService.java new file mode 100644 index 0000000..2661975 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/service/CommentTreeService.java @@ -0,0 +1,26 @@ +package com.tarento.commenthub.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.tarento.commenthub.dto.CommentTreeIdentifierDTO; +import com.tarento.commenthub.entity.CommentTree; +import java.util.List; + +public interface CommentTreeService { + + CommentTree createCommentTree(JsonNode payload); + + CommentTree updateCommentTree(JsonNode payload); + + CommentTree getCommentTreeById(String commentTreeId); + + CommentTree getCommentTree(CommentTreeIdentifierDTO commentTreeIdentifierDTO); + + void updateCommentTreeForDeletedComment(String commentId, + CommentTreeIdentifierDTO commentTreeIdentifierDTO); + + List<CommentTree> getAllCommentTreeForMultipleWorkflows(String entityType, String entityId, + List<String> workflows); + + CommentTree setCommentTreeStatusToResolved(CommentTreeIdentifierDTO commentTreeIdentifierDTO); + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/service/impl/CommentServiceImpl.java b/src/main/java/com/tarento/commenthub/service/impl/CommentServiceImpl.java new file mode 100644 index 0000000..e702012 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/service/impl/CommentServiceImpl.java @@ -0,0 +1,269 @@ +package com.tarento.commenthub.service.impl; + +import static com.tarento.commenthub.constant.Constants.COMMENT_KEY; +import static com.tarento.commenthub.utility.CommentsUtility.containsNull; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.uuid.Generators; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.ValidationMessage; +import com.tarento.commenthub.constant.Constants; +import com.tarento.commenthub.dto.CommentTreeIdentifierDTO; +import com.tarento.commenthub.dto.MultipleWorkflowsCommentResponseDTO; +import com.tarento.commenthub.dto.CommentsResoponseDTO; +import com.tarento.commenthub.dto.ResponseDTO; +import com.tarento.commenthub.entity.Comment; +import com.tarento.commenthub.entity.CommentTree; +import com.tarento.commenthub.exception.CommentException; +import com.tarento.commenthub.repository.CommentRepository; +import com.tarento.commenthub.service.CommentService; +import com.tarento.commenthub.service.CommentTreeService; +import com.tarento.commenthub.utility.Status; +import java.io.InputStream; +import java.sql.Timestamp; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class CommentServiceImpl implements CommentService { + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private CommentTreeService commentTreeService; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RedisTemplate redisTemplate; + + @Value("${redis.ttl}") + private long redisTtl; + + @Override + public ResponseDTO addFirstCommentToCreateTree(JsonNode payload) { + validatePayload(Constants.ADD_FIRST_COMMENT_PAYLOAD_VALIDATION_FILE, payload); + Comment comment = getPersistedComment(payload); + + ((ObjectNode) payload).put(Constants.COMMENT_ID, comment.getCommentId()); + CommentTree commentTree = commentTreeService.createCommentTree(payload); + + ResponseDTO responseDTO = new ResponseDTO(); + responseDTO.setComment(comment); + responseDTO.setCommentTree(commentTree); + return responseDTO; + } + + @Override + public ResponseDTO addNewCommentToTree(JsonNode payload) { + validatePayload(Constants.ADD_NEW_COMMENT_PAYLOAD_VALIDATION_FILE, payload); + Comment comment = getPersistedComment(payload); + ((ObjectNode) payload).put(Constants.COMMENT_ID, comment.getCommentId()); + CommentTree commentTree = commentTreeService.updateCommentTree(payload); + + ResponseDTO responseDTO = new ResponseDTO(); + responseDTO.setComment(comment); + responseDTO.setCommentTree(commentTree); + return responseDTO; + } + + @Override + public ResponseDTO updateExistingComment(JsonNode paylaod) { + validatePayload(Constants.UPDATE_EXISTING_COMMENT_VALIDATION_FILE, paylaod); + if (paylaod.get(Constants.COMMENT_ID) == null + || paylaod.get(Constants.COMMENT_ID).asText().isEmpty()) { + throw new CommentException( + Constants.ERROR, "To update an existing comment, please provide a valid commentId."); + } + + log.info("commentId: " + paylaod.get(Constants.COMMENT_ID).asText()); + + Optional<Comment> optComment = + commentRepository.findById(paylaod.get(Constants.COMMENT_ID).asText()); + + if (!optComment.isPresent() + || !optComment.get().getStatus().equalsIgnoreCase(Status.ACTIVE.name())) { + throw new CommentException( + Constants.ERROR, "The requested comment was not found or has been deleted."); + } + + Comment commentToBeUpdated = optComment.get(); + commentToBeUpdated.setCommentData(paylaod.get(Constants.COMMENT_DATA)); + + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + commentToBeUpdated.setLastUpdatedDate(currentTime); + Comment updatedComment = commentRepository.save(commentToBeUpdated); + redisTemplate.opsForValue() + .set(COMMENT_KEY + commentToBeUpdated.getCommentId(), updatedComment, redisTtl, + TimeUnit.SECONDS); + CommentTree commentTree = + commentTreeService.getCommentTreeById(paylaod.get(Constants.COMMENT_TREE_ID).asText()); + ResponseDTO responseDTO = new ResponseDTO(); + responseDTO.setComment(updatedComment); + responseDTO.setCommentTree(commentTree); + + return responseDTO; + } + + @Override + public CommentsResoponseDTO getComments(CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + CommentTree commentTree = commentTreeService.getCommentTree(commentTreeIdentifierDTO); + JsonNode childNodes = commentTree.getCommentTreeData().get(Constants.CHILD_NODES); + List<String> childNodeList = objectMapper.convertValue(childNodes, List.class); + log.info("CommentServiceImpl::getComments::fetch comments from redis"); + List<Comment> comments = redisTemplate.opsForValue().multiGet(getKeys(childNodeList)); + if (containsNull(comments)) { + log.info("CommentServiceImpl::getComments::fetch Comments from postgres"); + // Fetch from db and add fetched comments into redis + comments = commentRepository.findByCommentIdInAndStatus(childNodeList, + Status.ACTIVE.name().toLowerCase()); + comments.stream(). + forEach(comment -> + redisTemplate.opsForValue() + .set(COMMENT_KEY + comment.getCommentId(), comment, redisTtl, TimeUnit.SECONDS)); + } + CommentsResoponseDTO commentsResoponseDTO = new CommentsResoponseDTO(); + commentsResoponseDTO.setComments(comments); + commentsResoponseDTO.setCommentTree(commentTree); + Optional.ofNullable(comments) + .ifPresent(commentsList -> commentsResoponseDTO.setCommentCount(commentsList.size())); + return commentsResoponseDTO; + } + + @Override + public List<MultipleWorkflowsCommentResponseDTO> getComments(String entityType, String entityId, + List<String> workflowList) { + + List<CommentTree> commentTreeList = commentTreeService.getAllCommentTreeForMultipleWorkflows(entityType, + entityId, + workflowList); + return commentTreeList.stream() + .map(commentTree -> { + JsonNode childNodes = commentTree.getCommentTreeData().get(Constants.CHILD_NODES); + List<String> childNodeList = objectMapper.convertValue(childNodes, List.class); + log.info("CommentServiceImpl::getComments::fetch comments from redis"); + List<Comment> comments = redisTemplate.opsForValue().multiGet(getKeys(childNodeList)); + if (containsNull(comments)) { + log.info("CommentServiceImpl::getComments::fetch Comments from postgres"); + // Fetch from db and add fetched comments into redis + comments = commentRepository.findByCommentIdInAndStatus(childNodeList, + Status.ACTIVE.name().toLowerCase()); + comments.forEach(comment -> + redisTemplate.opsForValue() + .set(COMMENT_KEY + comment.getCommentId(), comment, redisTtl, TimeUnit.SECONDS) + ); + } + MultipleWorkflowsCommentResponseDTO multipleWorkflowsCommentResponseDTO = new MultipleWorkflowsCommentResponseDTO(); + multipleWorkflowsCommentResponseDTO.setCommentTree(commentTree); + Optional.ofNullable(comments) + .ifPresent(commentsList -> multipleWorkflowsCommentResponseDTO.setCommentCount( + commentsList.size())); + multipleWorkflowsCommentResponseDTO.setComments(comments); + return multipleWorkflowsCommentResponseDTO; + }) + .collect(Collectors.toList()); + } + + @Override + public Comment deleteCommentById( + String commentId, CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + log.info("CommentServiceImpl::deleteCommentById: Deleting comment with ID: {}", commentId); + Optional<Comment> fetchedComment = commentRepository.findById(commentId); + if (!fetchedComment.isPresent()) { + throw new CommentException(Constants.ERROR, "No such comment found"); + } + Comment comment = fetchedComment.get(); + if (!comment.getStatus().equalsIgnoreCase(Status.ACTIVE.name())) { + throw new CommentException( + Constants.ERROR, "You are trying to delete an already deleted comment"); + } + comment.setStatus(Status.INACTIVE.name().toLowerCase()); + comment = commentRepository.save(comment); + redisTemplate.opsForValue().getOperations().delete(COMMENT_KEY + commentId); + commentTreeService.updateCommentTreeForDeletedComment(commentId, commentTreeIdentifierDTO); + return comment; + } + + private String generateCommentId() { + UUID uuid = Generators.timeBasedGenerator().generate(); + return uuid.toString(); + } + + public Comment getPersistedComment(JsonNode commentPayload) { + Comment comment = new Comment(); + String commentId = generateCommentId(); + comment.setCommentId(commentId); + comment.setCommentData(commentPayload.get(Constants.COMMENT_DATA)); + // Set Status default value 'active' for new comment + comment.setStatus(Status.ACTIVE.name().toLowerCase()); + ObjectNode objectNode = (ObjectNode) comment.getCommentData(); + // Set commentResolved default value 'false' for new comment + objectNode.put(Constants.COMMENT_RESOLVED, Constants.FALSE); + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + comment.setCreatedDate(currentTime); + comment.setLastUpdatedDate(currentTime); + comment = commentRepository.save(comment); + redisTemplate.opsForValue() + .set(COMMENT_KEY + comment.getCommentId(), comment, redisTtl, TimeUnit.SECONDS); + return comment; + } + + public void validatePayload(String fileName, JsonNode payload) { + try { + JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(); + InputStream schemaStream = schemaFactory.getClass().getResourceAsStream(fileName); + JsonSchema schema = schemaFactory.getSchema(schemaStream); + + Set<ValidationMessage> validationMessages = schema.validate(payload); + if (!validationMessages.isEmpty()) { + StringBuilder errorMessage = new StringBuilder("Validation error(s): \n"); + for (ValidationMessage message : validationMessages) { + errorMessage.append(message.getMessage()).append("\n"); + } + throw new CommentException(Constants.ERROR, errorMessage.toString()); + } + } catch (Exception e) { + throw new CommentException(Constants.ERROR, "Failed to validate payload: " + e.getMessage()); + } + } + + private List<String> getKeys(List<String> childNodeList) { + return childNodeList.stream().map(id -> COMMENT_KEY + id) + .collect(Collectors.toList()); + } + + @Override + public Comment resolveComment(String commentId) { + log.info("CommentServiceImpl::resolveComment: Resolving comment with ID: {}", commentId); + Optional<Comment> fetchedComment = commentRepository.findById(commentId); + if (!fetchedComment.isPresent()) { + throw new CommentException(Constants.ERROR, "No such comment found"); + } + Comment comment = fetchedComment.get(); + ObjectNode objectNode = (ObjectNode) comment.getCommentData(); + objectNode.put(Constants.COMMENT_RESOLVED, Constants.TRUE); + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + comment.setLastUpdatedDate(currentTime); + comment = commentRepository.save(comment); + redisTemplate.opsForValue() + .set(COMMENT_KEY + commentId, comment, redisTtl, + TimeUnit.SECONDS); + return comment; + } + +} \ No newline at end of file diff --git a/src/main/java/com/tarento/commenthub/service/impl/CommentTreeServiceImpl.java b/src/main/java/com/tarento/commenthub/service/impl/CommentTreeServiceImpl.java new file mode 100644 index 0000000..9f84199 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/service/impl/CommentTreeServiceImpl.java @@ -0,0 +1,265 @@ +package com.tarento.commenthub.service.impl; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.tarento.commenthub.constant.Constants; +import com.tarento.commenthub.dto.CommentTreeIdentifierDTO; +import com.tarento.commenthub.entity.CommentTree; +import com.tarento.commenthub.exception.CommentException; +import com.tarento.commenthub.repository.CommentTreeRepository; +import com.tarento.commenthub.service.CommentTreeService; +import com.tarento.commenthub.utility.Status; +import java.sql.Timestamp; +import java.util.List; +import java.util.Optional; +import lombok.extern.log4j.Log4j2; +import net.bytebuddy.implementation.bytecode.Throw; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@Log4j2 +public class CommentTreeServiceImpl implements CommentTreeService { + + @Value("${jwt.secret.key}") + private String jwtSecretKey; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private CommentTreeRepository commentTreeRepository; + + public CommentTree createCommentTree(JsonNode payload) { + CommentTreeIdentifierDTO commentTreeIdentifierDTO = getCommentTreeIdentifierDTO( + payload.get(Constants.COMMENT_TREE_DATA)); + String commentTreeId = generateJwtTokenKey(commentTreeIdentifierDTO); + int commentTreeIdCount = commentTreeRepository.getIdCount(commentTreeId); + if (commentTreeIdCount > 0) { + throw new CommentException(Constants.DUPLICATE_TREE_ERROR, + Constants.DUPLICATE_TREE_ERROR_MESSAGE); + } + + try { + CommentTree commentTree = new CommentTree(); + commentTree.setCommentTreeId(commentTreeId); + // Set Status default value 'active' for new commentTree + commentTree.setStatus(Status.ACTIVE.name().toLowerCase()); + // Create an object node for a comment entry + ObjectNode commentEntryNode = objectMapper.createObjectNode(); + commentEntryNode.set(Constants.COMMENT_ID, payload.get(Constants.COMMENT_ID)); + + ObjectNode commentTreeObjNode = (ObjectNode) payload.get(Constants.COMMENT_TREE_DATA); + commentTreeObjNode.putArray(Constants.COMMENTS).add(commentEntryNode); + commentTreeObjNode.putArray(Constants.CHILD_NODES).add(payload.get(Constants.COMMENT_ID)); + commentTreeObjNode.putArray(Constants.FIRST_LEVEL_NODES) + .add(payload.get(Constants.COMMENT_ID)); + commentTree.setCommentTreeData(commentTreeObjNode); + + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + commentTree.setCreatedDate(currentTime); + commentTree.setLastUpdatedDate(currentTime); + + return commentTreeRepository.save(commentTree); + } catch (Exception e) { + e.printStackTrace(); + throw new CommentException(Constants.ERROR, e.getMessage()); + } + } + + public CommentTree updateCommentTree(JsonNode payload) { + CommentTree commentTree; + String commentTreeId = payload.get(Constants.COMMENT_TREE_ID).asText(); + Optional<CommentTree> optCommentTree = commentTreeRepository.findById(commentTreeId); + if (optCommentTree.isPresent()) { + commentTree = optCommentTree.get(); + JsonNode commentTreeJson = commentTree.getCommentTreeData(); + + try { + // Create an object node for a comment entry + ObjectNode commentEntryNode = objectMapper.createObjectNode(); + commentEntryNode.set(Constants.COMMENT_ID, payload.get(Constants.COMMENT_ID)); + + if (payload.get(Constants.HIERARCHY_PATH) != null && !payload.get( + Constants.HIERARCHY_PATH).isEmpty()) { + String[] hierarchyPath = objectMapper.treeToValue( + payload.get(Constants.HIERARCHY_PATH), String[].class); + // Find the target position based on the hierarchy path + JsonNode targetJsonNode = findTargetNode(commentTreeJson.get(Constants.COMMENTS), + hierarchyPath, 0); + if (targetJsonNode == null) { + throw new CommentException(Constants.ERROR, Constants.WRONG_HIERARCHY_PATH_ERROR); + } + if (targetJsonNode.isArray()) { + ArrayNode targetArrayNode = (ArrayNode) targetJsonNode; + targetArrayNode.add(commentEntryNode); + } else { + if (targetJsonNode.get(Constants.CHILDREN) != null) { + ArrayNode childrenArrayNode = (ArrayNode) targetJsonNode.get(Constants.CHILDREN); + childrenArrayNode.add(commentEntryNode); + } else { + ObjectNode targetObjectNode = (ObjectNode) targetJsonNode; + targetObjectNode.putArray(Constants.CHILDREN).add(commentEntryNode); + } + } + } else { + ArrayNode targetArrayNode = (ArrayNode) commentTreeJson.get(Constants.COMMENTS); + targetArrayNode.add(commentEntryNode); + // Retrieve the existing firstLevelNodes array + ArrayNode firstLevelNodesArray = (ArrayNode) commentTreeJson.get( + Constants.FIRST_LEVEL_NODES); + // Add the new comment ID to the existing firstLevelNodes array + firstLevelNodesArray.add(payload.get(Constants.COMMENT_ID)); + } + // Retrieve the existing childNodes array + ArrayNode childNodesArray = (ArrayNode) commentTreeJson.get(Constants.CHILD_NODES); + //Add the new comment ID to the existing childNodes array + childNodesArray.add(payload.get(Constants.COMMENT_ID)); + + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + commentTree.setLastUpdatedDate(currentTime); + return commentTreeRepository.save(commentTree); + } catch (Exception e) { + e.printStackTrace(); + throw new CommentException(Constants.ERROR, e.getMessage()); + } + } + return null; + } + + public static JsonNode findTargetNode(JsonNode currentNode, String[] hierarchyPath, int index) { + if (index >= hierarchyPath.length) { + return currentNode; + } + + String targetCommentId = hierarchyPath[index]; + if (currentNode.isArray()) { + for (JsonNode childNode : currentNode) { + if (childNode.isObject() && targetCommentId.equals( + childNode.get(Constants.COMMENT_ID).asText()) + && childNode.get(Constants.CHILDREN) != null) { + return findTargetNode(childNode.get(Constants.CHILDREN), hierarchyPath, index + 1); + } else if (childNode.isObject() && targetCommentId.equals( + childNode.get(Constants.COMMENT_ID).asText())) { + return findTargetNode(childNode, hierarchyPath, index + 1); + } + } + } + return null; + } + + @Override + public CommentTree getCommentTreeById(String commentTreeId) { + Optional<CommentTree> optionalCommentTree = commentTreeRepository.findById(commentTreeId); + if (optionalCommentTree.isPresent()) { + return optionalCommentTree.get(); + } + return null; + } + + @Override + public CommentTree getCommentTree(CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + String commentTreeId = generateJwtTokenKey(commentTreeIdentifierDTO); + Optional<CommentTree> optionalCommentTree = commentTreeRepository.findById(commentTreeId); + if (optionalCommentTree.isPresent()) { + return optionalCommentTree.get(); + } + throw new CommentException(Constants.ERROR, "Comment Tree not found"); + } + + @Override + public void updateCommentTreeForDeletedComment(String commentId, + CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + log.info("Updating comment tree for deleted comment with ID: {}", commentId); + Optional<CommentTree> optionalCommentTree = commentTreeRepository.findById( + generateJwtTokenKey(commentTreeIdentifierDTO)); + if (optionalCommentTree.isPresent()) { + CommentTree commentTreeToBeUpdated = optionalCommentTree.get(); + JsonNode jsonNode = commentTreeToBeUpdated.getCommentTreeData(); + + boolean commentIdFound = false; + // To remove commentId from childNodes + ArrayNode childNodes = (ArrayNode) jsonNode.get(Constants.CHILD_NODES); + for (int i = 0; i < childNodes.size(); i++) { + if (commentId.equals(childNodes.get(i).asText())) { + commentIdFound = true; + childNodes.remove(i); + break; // Exit the loop once the ID is found and removed + } + } + + if (!commentIdFound) { + throw new CommentException(Constants.ERROR, + "Comment, you're trying to delete not found in the specified comment tree." + + " Please double-check the 'entityType', 'entityId', and 'workflow' values to locate the correct comment tree."); + } + + // To remove commentId from firstLevelNodes + ArrayNode firstLevelNodes = (ArrayNode) jsonNode.get(Constants.FIRST_LEVEL_NODES); + for (int i = 0; i < firstLevelNodes.size(); i++) { + if (commentId.equals(firstLevelNodes.get(i).asText())) { + firstLevelNodes.remove(i); + break; // Exit the loop once the ID is found and removed + } + } + + commentTreeRepository.save(commentTreeToBeUpdated); + } + } + + public String generateJwtTokenKey(CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + log.info("generating JwtTokenKey"); + + if (StringUtils.isAnyBlank( + commentTreeIdentifierDTO.getEntityId(), + commentTreeIdentifierDTO.getEntityType(), + commentTreeIdentifierDTO.getWorkflow())) { + throw new CommentException(Constants.ERROR, + "Please provide values for 'entityType', 'entityId', and 'workflow' as all of these fields are mandatory."); + } + + String jwtToken = JWT.create() + .withClaim(Constants.ENTITY_ID, commentTreeIdentifierDTO.getEntityId()) + .withClaim(Constants.ENTITY_TYPE, commentTreeIdentifierDTO.getEntityType()) + .withClaim(Constants.WORKFLOW, commentTreeIdentifierDTO.getWorkflow()) + .sign(Algorithm.HMAC256(jwtSecretKey)); + + log.info("commentTreeId: {}", jwtToken); + return jwtToken; + } + + + public CommentTreeIdentifierDTO getCommentTreeIdentifierDTO(JsonNode commentTreeData) { + return new CommentTreeIdentifierDTO( + commentTreeData.get(Constants.ENTITY_TYPE).asText(), + commentTreeData.get(Constants.ENTITY_ID).asText(), + commentTreeData.get(Constants.WORKFLOW).asText() + ); + } + + @Override + public List<CommentTree> getAllCommentTreeForMultipleWorkflows(String entityType, String entityId, + List<String> workflowList) { + return commentTreeRepository.getAllCommentTreeForMultipleWorkflows(entityId, entityType, workflowList); + } + + @Override + public CommentTree setCommentTreeStatusToResolved( + CommentTreeIdentifierDTO commentTreeIdentifierDTO) { + Optional<CommentTree> optionalCommentTree = commentTreeRepository.findById( + generateJwtTokenKey(commentTreeIdentifierDTO)); + if(optionalCommentTree.isPresent()) { + CommentTree commentTree = optionalCommentTree.get(); + commentTree.setStatus(Constants.RESOLVED); + return commentTreeRepository.save(commentTree); + } + throw new CommentException(Constants.ERROR,"Comment Tree not found"); + } + +} diff --git a/src/main/java/com/tarento/commenthub/utility/CommentsUtility.java b/src/main/java/com/tarento/commenthub/utility/CommentsUtility.java new file mode 100644 index 0000000..904247a --- /dev/null +++ b/src/main/java/com/tarento/commenthub/utility/CommentsUtility.java @@ -0,0 +1,20 @@ +package com.tarento.commenthub.utility; + +import java.util.List; + +public class CommentsUtility { + + public static boolean containsNull(List<?> list) { + if (list == null) { + return true; + } + + for (Object element : list) { + if (element == null) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/tarento/commenthub/utility/Status.java b/src/main/java/com/tarento/commenthub/utility/Status.java new file mode 100644 index 0000000..4e407e5 --- /dev/null +++ b/src/main/java/com/tarento/commenthub/utility/Status.java @@ -0,0 +1,6 @@ +package com.tarento.commenthub.utility; + +public enum Status { + ACTIVE, + INACTIVE +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8640d25 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,32 @@ +server.port=8099 + +management.endpoints.web.exposure.include=health + +# PostgreSQL configuration +spring.datasource.url=jdbc:postgresql://localhost:5432/commentdb?currentSchema=comment +spring.datasource.username=postgres +spring.datasource.password=password +spring.datasource.driver-class-name=org.postgresql.Driver + +## Hibernate Properties +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect + +# Hibernate ddl auto (create, create-drop, validate, update) +spring.jpa.hibernate.ddl-auto = update + +#------------------secretes------------# +jwt.secret.key=comment-hub + +#----------------------------------Redis ----------------------------- +spring.redis.host=127.0.0.1 +spring.redis.port=6379 +#spring.redis.password=KZ9u%Z&mki4&p35 +# 14 days in second 14 * 24 * 60 * 60 +redis.ttl=1209600 + +#----------------------------Configure Logback pattern ----------------------- +logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n +logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n +# Log to console and file +logging.file=logs/commentHub.log \ No newline at end of file diff --git a/src/main/resources/payloadValidation/firstComment.json b/src/main/resources/payloadValidation/firstComment.json new file mode 100644 index 0000000..bd6ff6b --- /dev/null +++ b/src/main/resources/payloadValidation/firstComment.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "commentTreeData": { + "type": "object", + "properties": { + "entityId": { + "type": "string", + "minLength": 1 + }, + "entityType": { + "type": "string", + "minLength": 1 + }, + "workflow": { + "type": "string", + "minLength": 1 + } + }, + "required": ["entityId", "entityType", "workflow"], + "additionalProperties": false + }, + "commentData": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1 + }, + "file": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "commentSource": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "minLength": 1 + }, + "userPic": { + "type": "string", + "minLength": 1 + }, + "userName": { + "type": "string" + } + }, + "required": ["userId", "userPic"], + "additionalProperties": false + } + }, + "required": ["comment", "commentSource"], + "additionalProperties": false + } + }, + "required": ["commentTreeData", "commentData"], + "additionalProperties": false +} diff --git a/src/main/resources/payloadValidation/newComment.json b/src/main/resources/payloadValidation/newComment.json new file mode 100644 index 0000000..0e6d0c3 --- /dev/null +++ b/src/main/resources/payloadValidation/newComment.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "commentTreeId": { + "type": "string" + }, + "hierarchyPath": { + "type": "array", + "items": { + "type": "string" + } + }, + "commentData": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1 + }, + "file": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "commentSource": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "minLength": 1 + }, + "userPic": { + "type": "string", + "minLength": 1 + }, + "userName": { + "type": "string" + } + }, + "required": ["userId", "userPic"], + "additionalProperties": false + } + }, + "required": ["comment", "commentSource"], + "additionalProperties": false + } + }, + "required": ["commentTreeId", "commentData"], + "additionalProperties": false +} \ No newline at end of file diff --git a/src/main/resources/payloadValidation/updateComment.json b/src/main/resources/payloadValidation/updateComment.json new file mode 100644 index 0000000..90cac47 --- /dev/null +++ b/src/main/resources/payloadValidation/updateComment.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "commentTreeId": { + "type": "string" + }, + "commentId": { + "type": "string" + }, + "commentData": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1 + }, + "file": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "commentSource": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "minLength": 1 + }, + "userPic": { + "type": "string", + "minLength": 1 + }, + "userName": { + "type": "string" + } + }, + "required": ["userId", "userPic"], + "additionalProperties": false + }, + "commentResolved": { + "type": "string", + "minLength": 1 + } + }, + "required": ["comment", "commentSource", "commentResolved"], + "additionalProperties": false + } + }, + "required": ["commentTreeId", "commentId", "commentData"], + "additionalProperties": false +} \ No newline at end of file diff --git a/src/test/java/com/tarento/commenthub/CommentHubApplicationTests.java b/src/test/java/com/tarento/commenthub/CommentHubApplicationTests.java new file mode 100644 index 0000000..834aaff --- /dev/null +++ b/src/test/java/com/tarento/commenthub/CommentHubApplicationTests.java @@ -0,0 +1,13 @@ +package com.tarento.commenthub; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CommentHubApplicationTests { + + @Test + void contextLoads() { + } + +} -- GitLab