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