diff --git a/.gitignore b/.gitignore index 9657bf9..abbafae 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ application.properties .settings/ .project .classpath -.vscode/ \ No newline at end of file +.vscode/ +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1efbdd7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# -------- Build stage -------- +FROM maven:3.9.6-eclipse-temurin-21 AS build + +WORKDIR /app + +COPY pom.xml . +RUN mvn -B dependency:go-offline + +COPY src ./src +RUN mvn -B clean package -DskipTests + +# -------- Runtime stage -------- +FROM eclipse-temurin:21-jre + +WORKDIR /app + +COPY --from=build /app/target/*.jar app.jar + +EXPOSE 9010 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/src/main/java/org/zaine/app/controller/CommentsController.java b/src/main/java/org/zaine/app/controller/CommentsController.java index 9c6bfd6..83e6b7f 100644 --- a/src/main/java/org/zaine/app/controller/CommentsController.java +++ b/src/main/java/org/zaine/app/controller/CommentsController.java @@ -7,12 +7,15 @@ import org.springframework.beans.factory.annotation.Autowired; 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.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.zaine.app.dto.CreateCommentDTO; import org.zaine.app.model.Comments; import org.zaine.app.service.CommentsService; @RestController +@RequestMapping("/api/comments") public class CommentsController { private static final Logger logger = System.getLogger(CommentsController.class.getName()); @Autowired @@ -25,14 +28,44 @@ public class CommentsController { } - @GetMapping("/comments/item/{id}") - public Comments getCommentById(@PathVariable Long id) { + @GetMapping("/item/{id}") + public Comments getCommentById(@PathVariable Integer id) { return commentsService.getCommentById(id); } - @GetMapping("/comments/items") + @GetMapping("") public List getAllComments() { return commentsService.getAllComments(); } + + @PostMapping( + value = "", + consumes = "application/json" + ) + public void addComment(@RequestBody CreateCommentDTO dto) { + + if (dto.getContent() == null || dto.getContent().trim().isEmpty()) { + logger.log(System.Logger.Level.WARNING, + "Attempted to add empty comment. Operation aborted."); + return; + } + + Comments newComment = new Comments(); + newComment.setPageSlug(dto.getPageSlug()); + newComment.setAuthor(dto.getAuthor()); + newComment.setContent(dto.getContent()); + newComment.setParentId(dto.getParentId()); + newComment.setCreatedAt(java.time.Instant.now()); + + logger.log(System.Logger.Level.INFO, + "Adding comment to pageSlug: " + dto.getPageSlug()); + + commentsService.addComment(newComment); + } + + @GetMapping("/thread/{comment_id}") + public List getCommentThread(@PathVariable Integer comment_id) { + return commentsService.getCommentThread(comment_id); + } } diff --git a/src/main/java/org/zaine/app/controller/CompetenciesController.java b/src/main/java/org/zaine/app/controller/CompetenciesController.java index a513e32..6af277a 100644 --- a/src/main/java/org/zaine/app/controller/CompetenciesController.java +++ b/src/main/java/org/zaine/app/controller/CompetenciesController.java @@ -2,41 +2,48 @@ package org.zaine.app.controller; import java.lang.System.Logger; import java.util.List; +import java.util.Map; +import org.zaine.app.dto.CompetenciesDTO; import org.springframework.beans.factory.annotation.Autowired; 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.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.zaine.app.model.Competencies; import org.zaine.app.service.CompetenciesService; + @RestController +@RequestMapping("/api/competencies") public class CompetenciesController { private static final Logger logger = System.getLogger(CompetenciesController.class.getName()); @Autowired private CompetenciesService competenciesService; - @GetMapping("/competencies/items") + @GetMapping("/items") public List getAllCompetencies() { return competenciesService.getAllCompetencies(); } - @GetMapping("/competencies/item/{id}") - public Competencies getCompetencyById(@PathVariable Long id) { + @GetMapping("/item/{id}") + public Competencies getCompetencyById(@PathVariable Integer id) { return competenciesService.getCompetencyById(id); } - - @PostMapping("/competencies/items/{id}/state") - public void updateCompetencyState(@PathVariable Long id, @RequestParam String newState) { - logger.log(System.Logger.Level.INFO, "Received request to update competency id " + id + " to new state: " + newState); - - competenciesService.updateCompetencyState(id, newState); - + @PostMapping( + value = "/items/{id}/state", + consumes = "application/json" + ) + public void updateCompetencyState( + @PathVariable Integer id, + @RequestBody CompetenciesDTO request + ) { + competenciesService.updateCompetencyState(id, request.getState()); } } diff --git a/src/main/java/org/zaine/app/controller/HealthController.java b/src/main/java/org/zaine/app/controller/HealthController.java index 02f9bb4..cc34ba8 100644 --- a/src/main/java/org/zaine/app/controller/HealthController.java +++ b/src/main/java/org/zaine/app/controller/HealthController.java @@ -1,4 +1,4 @@ -package org.zaine.app; +package org.zaine.app.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/org/zaine/app/controller/NotesController.java b/src/main/java/org/zaine/app/controller/NotesController.java index ca8a862..d75e2e8 100644 --- a/src/main/java/org/zaine/app/controller/NotesController.java +++ b/src/main/java/org/zaine/app/controller/NotesController.java @@ -7,11 +7,13 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.zaine.app.dto.CreateNoteDTO; import org.zaine.app.model.Notes; import org.zaine.app.service.NotesService; import java.util.List; @RestController +@RequestMapping("/api") public class NotesController { @Autowired private NotesService notesService; @@ -22,10 +24,19 @@ public class NotesController { return notesService.getAllNotes(); } - @PostMapping("/notes") - public Notes createNote(@RequestBody Notes note) { + @PostMapping( + value = "/notes", + consumes = "application/json" + ) + public Notes createNote(@RequestBody CreateNoteDTO dto) { + + Notes note = new Notes(); + note.setAuthorName(dto.getAuthorName()); + note.setContent(dto.getContent()); + return notesService.createNote(note); } + diff --git a/src/main/java/org/zaine/app/dto/CompetenciesDTO.java b/src/main/java/org/zaine/app/dto/CompetenciesDTO.java new file mode 100644 index 0000000..3b68ebf --- /dev/null +++ b/src/main/java/org/zaine/app/dto/CompetenciesDTO.java @@ -0,0 +1,18 @@ +package org.zaine.app.dto; + +public class CompetenciesDTO { + + private String state; + + public CompetenciesDTO() { + // REQUIRED: default constructor + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/src/main/java/org/zaine/app/dto/CreateCommentDTO.java b/src/main/java/org/zaine/app/dto/CreateCommentDTO.java new file mode 100644 index 0000000..8766e95 --- /dev/null +++ b/src/main/java/org/zaine/app/dto/CreateCommentDTO.java @@ -0,0 +1,50 @@ +package org.zaine.app.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CreateCommentDTO { + + @JsonProperty("page_slug") + private String pageSlug; + + private String author; + + private String content; + + @JsonProperty("parent_id") + private Integer parentId; + + public CreateCommentDTO() {} + + public String getPageSlug() { + return pageSlug; + } + + public void setPageSlug(String pageSlug) { + this.pageSlug = pageSlug; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } +} diff --git a/src/main/java/org/zaine/app/dto/CreateNoteDTO.java b/src/main/java/org/zaine/app/dto/CreateNoteDTO.java new file mode 100644 index 0000000..8e4896c --- /dev/null +++ b/src/main/java/org/zaine/app/dto/CreateNoteDTO.java @@ -0,0 +1,29 @@ +package org.zaine.app.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CreateNoteDTO { + + @JsonProperty("author_name") + private String authorName; + + private String content; + + public CreateNoteDTO() {} + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/org/zaine/app/model/Comments.java b/src/main/java/org/zaine/app/model/Comments.java index ee9b506..920327f 100644 --- a/src/main/java/org/zaine/app/model/Comments.java +++ b/src/main/java/org/zaine/app/model/Comments.java @@ -1,9 +1,13 @@ package org.zaine.app.model; import java.time.Instant; + +import com.fasterxml.jackson.annotation.JsonProperty; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -12,9 +16,9 @@ import jakarta.persistence.Table; public class Comments { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - private Long id; + private Integer id; @Column(name = "page_slug") private String pageSlug; @@ -25,17 +29,19 @@ public class Comments { @Column(name="content") private String content; + @JsonProperty("created_at") @Column(name="created_at") private Instant createdAt; + @JsonProperty("parent_id") @Column(name="parent_id") - private Long parentId; + private Integer parentId; - public Long getId() { + public Integer getId() { return id; } - public void setId(Long id) { + public void setId(Integer id) { this.id = id; } public String getPageSlug() { @@ -62,10 +68,10 @@ public class Comments { public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } - public Long getParentId() { + public Integer getParentId() { return parentId; } - public void setParentId(Long parentId) { + public void setParentId(Integer parentId) { this.parentId = parentId; } diff --git a/src/main/java/org/zaine/app/model/Competencies.java b/src/main/java/org/zaine/app/model/Competencies.java index 9fd263a..72c59a2 100644 --- a/src/main/java/org/zaine/app/model/Competencies.java +++ b/src/main/java/org/zaine/app/model/Competencies.java @@ -6,15 +6,16 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; @Entity @Table(name = "competencies") public class Competencies { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") - private Long id; + private Integer id; @Column(name="title") private String title; @@ -29,7 +30,7 @@ public class Competencies { private String group; - public Long getId() { + public Integer getId() { return id; } diff --git a/src/main/java/org/zaine/app/model/Notes.java b/src/main/java/org/zaine/app/model/Notes.java index 4c978b2..c72e277 100644 --- a/src/main/java/org/zaine/app/model/Notes.java +++ b/src/main/java/org/zaine/app/model/Notes.java @@ -9,6 +9,8 @@ import jakarta.persistence.GeneratedValue; import java.time.Instant; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonProperty; + @Entity @Table(name = "public_notes") public class Notes { @@ -21,11 +23,14 @@ public class Notes { @Column(name="content") private String content; + @JsonProperty("author_name") @Column(name="author_name") - private String authorName; + private String authorName; + @JsonProperty("created_at") @Column(name="created_at") - private Instant createdAt; + private Instant createdAt; + public UUID getId() { return id; @@ -51,5 +56,9 @@ public class Notes { return createdAt; } + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } diff --git a/src/main/java/org/zaine/app/repositories/CommentsRepository.java b/src/main/java/org/zaine/app/repositories/CommentsRepository.java index 313c98b..8984b27 100644 --- a/src/main/java/org/zaine/app/repositories/CommentsRepository.java +++ b/src/main/java/org/zaine/app/repositories/CommentsRepository.java @@ -1,8 +1,29 @@ package org.zaine.app.repositories; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.zaine.app.model.Comments; -public interface CommentsRepository extends JpaRepository { +public interface CommentsRepository extends JpaRepository { + @Query(value = """ + WITH RECURSIVE comment_tree AS ( + SELECT id, parent_id, author, content, created_at, page_slug + FROM comments + WHERE id = :commentId + AND is_deleted = false + UNION ALL + + SELECT c.id, c.parent_id, c.author, c.content, c.created_at, c.page_slug + FROM comments c + JOIN comment_tree ct ON c.parent_id = ct.id + WHERE c.is_deleted = false + ) + SELECT * FROM comment_tree + ORDER BY created_at ASC + """, nativeQuery = true) + List findCommentThread(@Param("commentId") Integer commentId); } diff --git a/src/main/java/org/zaine/app/repositories/CompetenciesRepository.java b/src/main/java/org/zaine/app/repositories/CompetenciesRepository.java index e983772..66afcd7 100644 --- a/src/main/java/org/zaine/app/repositories/CompetenciesRepository.java +++ b/src/main/java/org/zaine/app/repositories/CompetenciesRepository.java @@ -4,6 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.zaine.app.model.Competencies; -public interface CompetenciesRepository extends JpaRepository { +public interface CompetenciesRepository extends JpaRepository { } diff --git a/src/main/java/org/zaine/app/service/CommentsService.java b/src/main/java/org/zaine/app/service/CommentsService.java index 62e7a6c..9b7c022 100644 --- a/src/main/java/org/zaine/app/service/CommentsService.java +++ b/src/main/java/org/zaine/app/service/CommentsService.java @@ -30,9 +30,25 @@ public class CommentsService { return filteredComments; } - public Comments getCommentById(Long id) { + public Comments getCommentById(Integer id) { logger.log(System.Logger.Level.INFO, "Entering getCommentById with id: " + id); return commentsRepository.findById(id).get(); } -} + public void addComment(Comments comment) { + if (comment == null) { + logger.log(System.Logger.Level.WARNING, "Comment is null. Operation aborted."); + return; + } else { + logger.log(System.Logger.Level.INFO, "Adding comment with id: " + comment.getId()); + commentsRepository.save(comment); + logger.log(System.Logger.Level.INFO, "Added new comment with id: " + comment.getId()); + + } + } + + public List getCommentThread(Integer comment_id) { + logger.log(System.Logger.Level.INFO, "Entering getCommentThread with id: " + comment_id); + return commentsRepository.findCommentThread(comment_id); + } +} diff --git a/src/main/java/org/zaine/app/service/CompetenciesService.java b/src/main/java/org/zaine/app/service/CompetenciesService.java index 5582075..5e9ade9 100644 --- a/src/main/java/org/zaine/app/service/CompetenciesService.java +++ b/src/main/java/org/zaine/app/service/CompetenciesService.java @@ -26,7 +26,7 @@ public class CompetenciesService { } - public Competencies getCompetencyById(Long id) { + public Competencies getCompetencyById(Integer id) { logger.log(System.Logger.Level.INFO, "Entering getCompetencyById with id: " + id); @@ -34,21 +34,19 @@ public class CompetenciesService { } - public void updateCompetencyState(Long id, String newState) { - Competencies competency = getCompetencyById(id); - - if (!ALLOWED_STATES.contains(newState)) { - logger.log(System.Logger.Level.WARNING, "Invalid state: " + newState); - throw new IllegalArgumentException("Invalid state: " + newState); - } + public void updateCompetencyState(Integer id, String newState) { - if (competency != null) { - competency.setState(newState); + if (newState == null) { + throw new IllegalArgumentException("State must not be null"); + } + + if (!ALLOWED_STATES.contains(newState)) { + throw new IllegalArgumentException("Invalid state: " + newState); + } + + Competencies competency = getCompetencyById(id); + competency.setState(newState); competenciesRepository.save(competency); - logger.log(System.Logger.Level.INFO, "Updated competency id " + id + " to new state: " + newState); - } else { - logger.log(System.Logger.Level.WARNING, "Competency with id " + id + " not found."); - } - } + } diff --git a/src/test/java/org/zaine/app/service/CompetenciesServiceTest.java b/src/test/java/org/zaine/app/service/CompetenciesServiceTest.java index effb504..0ca2a31 100644 --- a/src/test/java/org/zaine/app/service/CompetenciesServiceTest.java +++ b/src/test/java/org/zaine/app/service/CompetenciesServiceTest.java @@ -40,7 +40,7 @@ public class CompetenciesServiceTest { @Test public void testGetCompetencyByIdReturnsCompetency() { Competencies comp = new Competencies(); - @NotNull Long id = comp.getId(); + @NotNull Integer id = comp.getId(); when(competenciesRepository.findById(id)).thenReturn(Optional.of(comp));