diff --git a/.gitignore b/.gitignore
index a2a3040..173b6aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,11 @@ build/
### VS Code ###
.vscode/
+
+### TexRendering ###
+*.png
+*.tex
+*.pdf
+*.log
+*.aux
+*.svg
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 0f72c5a..7c96e8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,6 +134,15 @@
1.10.7
test
+
+ org.springframework
+ spring-test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.10.0
+
diff --git a/src/main/java/com/patternpedia/api/entities/DiscussionComment.java b/src/main/java/com/patternpedia/api/entities/DiscussionComment.java
new file mode 100644
index 0000000..ab7c945
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/entities/DiscussionComment.java
@@ -0,0 +1,34 @@
+package com.patternpedia.api.entities;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.*;
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+import java.util.Date;
+import java.util.UUID;
+
+
+
+@Entity
+@Data
+@NoArgsConstructor
+@Getter
+@Setter
+public class DiscussionComment {
+ @Id
+ @GeneratedValue(generator = "uuid")
+ @GenericGenerator(name = "uuid", strategy = "uuid2")
+ private UUID id;
+ private String text;
+ private UUID replyTo;
+ private Date date;
+
+ @JsonIgnore
+ @ToString.Exclude
+ @ManyToOne
+ private DiscussionTopic discussionTopic;
+
+
+}
diff --git a/src/main/java/com/patternpedia/api/entities/DiscussionTopic.java b/src/main/java/com/patternpedia/api/entities/DiscussionTopic.java
new file mode 100644
index 0000000..1896931
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/entities/DiscussionTopic.java
@@ -0,0 +1,41 @@
+package com.patternpedia.api.entities;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.patternpedia.api.rest.model.Status;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+@Entity
+@Data
+@Getter
+@Setter
+@NoArgsConstructor
+public class DiscussionTopic {
+ @Id
+ @GeneratedValue(generator = "uuid")
+ @GenericGenerator(name = "uuid", strategy = "uuid2")
+ private UUID id;
+ private String title;
+ private String description;
+ private Status status;
+ private Date date;
+ private Double x;
+ private Double y;
+ private Double width;
+ private Double height;
+ private String fill;
+ private UUID imageId;
+
+ @JsonIgnore
+ @OneToMany(mappedBy = "discussionTopic", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ private List discussionComments;
+}
+
diff --git a/src/main/java/com/patternpedia/api/entities/Image.java b/src/main/java/com/patternpedia/api/entities/Image.java
new file mode 100644
index 0000000..f0fc2cd
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/entities/Image.java
@@ -0,0 +1,29 @@
+package com.patternpedia.api.entities;
+
+import lombok.*;
+
+import org.hibernate.annotations.GenericGenerator;
+
+
+import javax.persistence.*;
+import java.util.UUID;
+
+@Entity
+@Data
+@NoArgsConstructor
+@Getter
+@Setter
+public class Image {
+
+ @Id
+ @GeneratedValue(generator = "uuid")
+ @GenericGenerator(name = "uuid", strategy = "uuid2")
+ private UUID id;
+
+ private String fileName;
+
+ private String fileType;
+
+ @Lob
+ private byte[] data;
+}
diff --git a/src/main/java/com/patternpedia/api/entities/Pattern.java b/src/main/java/com/patternpedia/api/entities/Pattern.java
index fc00d6c..8fc8cce 100644
--- a/src/main/java/com/patternpedia/api/entities/Pattern.java
+++ b/src/main/java/com/patternpedia/api/entities/Pattern.java
@@ -38,4 +38,8 @@ public class Pattern extends EntityWithURI {
@Column(columnDefinition = "jsonb")
@NotNull
private Object content;
+
+ @Type(type = "jsonb")
+ @Column(columnDefinition = "jsonb")
+ private Object renderedContent;
}
diff --git a/src/main/java/com/patternpedia/api/repositories/DiscussionCommentRepository.java b/src/main/java/com/patternpedia/api/repositories/DiscussionCommentRepository.java
new file mode 100644
index 0000000..2b1671d
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/repositories/DiscussionCommentRepository.java
@@ -0,0 +1,14 @@
+package com.patternpedia.api.repositories;
+
+import com.patternpedia.api.entities.DiscussionComment;
+import com.patternpedia.api.entities.DiscussionTopic;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+
+import java.util.List;
+import java.util.UUID;
+
+@RepositoryRestResource(exported = false)
+public interface DiscussionCommentRepository extends CrudRepository {
+ List findDiscussionCommentByDiscussionTopic(DiscussionTopic discussionTopic);
+}
diff --git a/src/main/java/com/patternpedia/api/repositories/DiscussionTopicRepository.java b/src/main/java/com/patternpedia/api/repositories/DiscussionTopicRepository.java
new file mode 100644
index 0000000..89161a7
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/repositories/DiscussionTopicRepository.java
@@ -0,0 +1,14 @@
+package com.patternpedia.api.repositories;
+
+import com.patternpedia.api.entities.DiscussionTopic;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+
+import java.util.List;
+import java.util.UUID;
+
+@RepositoryRestResource(exported = false)
+public interface DiscussionTopicRepository extends CrudRepository {
+ List findDiscussionTopicsByImageId(UUID imageId);
+}
+
diff --git a/src/main/java/com/patternpedia/api/repositories/ImageRepository.java b/src/main/java/com/patternpedia/api/repositories/ImageRepository.java
new file mode 100644
index 0000000..b6c9cbe
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/repositories/ImageRepository.java
@@ -0,0 +1,10 @@
+package com.patternpedia.api.repositories;
+
+import com.patternpedia.api.entities.Image;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+import java.util.UUID;
+
+@RepositoryRestResource(exported = false)
+public interface ImageRepository extends CrudRepository {
+}
diff --git a/src/main/java/com/patternpedia/api/rest/controller/DiscussionController.java b/src/main/java/com/patternpedia/api/rest/controller/DiscussionController.java
new file mode 100644
index 0000000..c321190
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/rest/controller/DiscussionController.java
@@ -0,0 +1,83 @@
+package com.patternpedia.api.rest.controller;
+
+import com.patternpedia.api.entities.DiscussionComment;
+import com.patternpedia.api.entities.DiscussionTopic;
+import com.patternpedia.api.rest.model.DiscussionTopicModel;
+import com.patternpedia.api.service.DiscussionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+
+@RestController
+@CrossOrigin(allowedHeaders = "*", origins = "*")
+public class DiscussionController {
+
+ private DiscussionService discussionService;
+
+ @Autowired
+ public DiscussionController(DiscussionService discussionService) {
+ this.discussionService = discussionService;
+ }
+
+ @ResponseStatus(HttpStatus.CREATED)
+ @PostMapping(
+ value = "/add-topic"
+ )
+ public @ResponseBody
+ DiscussionTopic addDiscussionTopic(@RequestBody DiscussionTopic topic){
+ // deepcode ignore XSS:
+ return this.discussionService.createTopic(topic);
+ }
+
+ @DeleteMapping(
+ value = "/delete-topic/{topicId}"
+ )
+ public @ResponseBody
+ ResponseEntity> deleteDiscussionTopic(@PathVariable UUID topicId){
+ this.discussionService.deleteTopicById(topicId);
+ return ResponseEntity.noContent().build();
+ }
+
+ @ResponseStatus(HttpStatus.CREATED)
+ @PostMapping(
+ value = "/add-comment/{topicId}"
+ )
+ public @ResponseBody
+ DiscussionComment addDiscussionComment(@PathVariable UUID topicId, @RequestBody DiscussionComment comment){
+ comment.setDiscussionTopic(this.discussionService.getTopicById(topicId));
+ // deepcode ignore XSS:
+ return this.discussionService.createComment(comment);
+ }
+
+ @GetMapping(
+ value = "/get-comments-by-topic/{topicId}"
+ )
+ public @ResponseBody
+ List getCommentsByTopic(@PathVariable UUID topicId){
+ // deepcode ignore XSS:
+ return this.discussionService.getCommentsByTopicId(topicId);
+ }
+
+ @GetMapping(
+ value = "/get-topic-by-image/{imageId}"
+ )
+ public @ResponseBody
+ List getTopicsByImageId(@PathVariable UUID imageId){
+ // deepcode ignore XSS:
+ return this.discussionService.getTopicsByImageId(imageId);
+ }
+
+ @GetMapping(
+ value = "/get-topics-and-comments-by-image/{imageId}"
+ )
+ public @ResponseBody
+ List getTopicsAndCommentsByImageId(@PathVariable UUID imageId){
+ // deepcode ignore XSS:
+ return this.discussionService.getTopicsAndCommentsByImageId(imageId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/patternpedia/api/rest/controller/ImageController.java b/src/main/java/com/patternpedia/api/rest/controller/ImageController.java
new file mode 100644
index 0000000..b78aae6
--- /dev/null
+++ b/src/main/java/com/patternpedia/api/rest/controller/ImageController.java
@@ -0,0 +1,75 @@
+package com.patternpedia.api.rest.controller;
+
+import com.patternpedia.api.entities.Image;
+import com.patternpedia.api.rest.model.ImageModel;
+import com.patternpedia.api.service.DiscussionService;
+import com.patternpedia.api.service.ImageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+
+import java.util.UUID;
+
+
+
+@RestController
+@CrossOrigin(allowedHeaders = "*", origins = "*")
+public class ImageController {
+
+ private ImageService imageService;
+ private DiscussionService discussionService;
+
+ @Autowired
+ public ImageController(ImageService imageService, DiscussionService discussionService) {
+ this.imageService = imageService;
+ this.discussionService = discussionService;
+ }
+
+
+ @GetMapping(
+ value = "/get-image-by-id/{imageId}",
+ produces = "image/svg+xml"
+ )
+ public @ResponseBody
+ byte[] getImageById(@PathVariable UUID imageId){
+ // deepcode ignore XSS: Returning by service created content via uuid
+ return this.imageService.getImageById(imageId).getData();
+ }
+
+ @PostMapping(
+ value = "/update-image/{imageId}",
+ produces = "image/svg+xml"
+ )
+ public @ResponseBody
+ byte[] updateImage(@PathVariable UUID imageId, @RequestBody byte[] data){
+ Image image = new Image();
+ image.setId(imageId);
+ image.setData(data);
+ image.setFileName(imageId.toString());
+ image.setFileType("image/svg+xml");
+ return this.imageService.updateImage(image).getData();
+ }
+
+
+ @ResponseStatus(HttpStatus.CREATED)
+ @PostMapping(
+ value = "/add-image",
+ produces = "application/json"
+ )
+ public @ResponseBody
+ Image addImage(@RequestBody Image image){
+ // deepcode ignore XSS:
+ return this.imageService.createImage(image);
+
+ }
+
+ @GetMapping(
+ value = "/get-image-and-comments-by-id/{imageId}"
+ )
+ public @ResponseBody
+ ImageModel getImageAndCommentsById(@PathVariable UUID imageId){
+ // deepcode ignore XSS: Returning by service created content via uuid
+ return new ImageModel(this.imageService.getImageById(imageId).getData(), this.discussionService.getTopicsAndCommentsByImageId(imageId));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/patternpedia/api/rest/controller/PatternController.java b/src/main/java/com/patternpedia/api/rest/controller/PatternController.java
index 08124c1..95fa9a7 100644
--- a/src/main/java/com/patternpedia/api/rest/controller/PatternController.java
+++ b/src/main/java/com/patternpedia/api/rest/controller/PatternController.java
@@ -10,12 +10,9 @@
import com.patternpedia.api.exception.DirectedEdgeNotFoundException;
import com.patternpedia.api.exception.UndirectedEdgeNotFoundException;
import com.patternpedia.api.rest.model.PatternContentModel;
+import com.patternpedia.api.rest.model.PatternRenderedContentModel;
import com.patternpedia.api.rest.model.PatternModel;
-import com.patternpedia.api.service.PatternLanguageService;
-import com.patternpedia.api.service.PatternRelationDescriptorService;
-import com.patternpedia.api.service.PatternService;
-import com.patternpedia.api.service.PatternViewService;
-
+import com.patternpedia.api.service.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.validation.Valid;
@@ -50,17 +47,20 @@ public class PatternController {
private PatternViewService patternViewService;
private PatternRelationDescriptorService patternRelationDescriptorService;
private ObjectMapper objectMapper;
+ private PatternRenderService patternRenderService;
public PatternController(PatternService patternService,
PatternLanguageService patternLanguageService,
PatternViewService patternViewService,
PatternRelationDescriptorService patternRelationDescriptorService,
+ PatternRenderService patternRenderService,
ObjectMapper objectMapper) {
this.patternService = patternService;
this.patternLanguageService = patternLanguageService;
this.patternViewService = patternViewService;
this.patternRelationDescriptorService = patternRelationDescriptorService;
this.objectMapper = objectMapper;
+ this.patternRenderService = patternRenderService;
}
static List getPatternLanguagePatternCollectionLinks(UUID patternLanguageId) {
@@ -90,6 +90,7 @@ List getPatternLinks(Pattern pattern) {
.andAffordance(afford(methodOn(PatternController.class).updatePatternViaPut(pattern.getPatternLanguage().getId(), pattern.getId(), null)))
.andAffordance(afford(methodOn(PatternController.class).deletePatternOfPatternLanguage(pattern.getPatternLanguage().getId(), pattern.getId()))));
links.add(linkTo(methodOn(PatternController.class).getPatternContentOfPattern(pattern.getPatternLanguage().getId(), pattern.getId())).withRel("content"));
+ links.add(linkTo(methodOn(PatternController.class).getPatternRenderedContentOfPattern(pattern.getPatternLanguage().getId(), pattern.getId())).withRel("renderedContent"));
links.add(linkTo(methodOn(PatternLanguageController.class).getPatternLanguageById(pattern.getPatternLanguage().getId())).withRel("patternLanguage"));
if (null != pattern.getPatternViews()) {
@@ -261,6 +262,7 @@ CollectionModel> getPatternsOfPatternLanguage(@PathVar
.map(pattern -> new EntityModel<>(PatternModel.from(pattern),
linkTo(methodOn(PatternController.class).getPatternOfPatternLanguageById(patternLanguageId, pattern.getId())).withSelfRel(),
linkTo(methodOn(PatternController.class).getPatternContentOfPattern(patternLanguageId, pattern.getId())).withRel("content"),
+ linkTo(methodOn(PatternController.class).getPatternRenderedContentOfPattern(patternLanguageId, pattern.getId())).withRel("renderedContent"),
linkTo(methodOn(PatternLanguageController.class).getPatternLanguageById(patternLanguageId)).withRel("patternLanguage")))
.collect(Collectors.toList());
return new CollectionModel<>(patterns, getPatternLanguagePatternCollectionLinks(patternLanguageId));
@@ -314,7 +316,13 @@ CollectionModel> getPatternsOfPatternView(@PathVariabl
@PostMapping(value = "/patternViews/{patternViewId}/patterns")
@CrossOrigin(exposedHeaders = "Location")
@ResponseStatus(HttpStatus.CREATED)
- public ResponseEntity> addPatternToPatternView(@PathVariable UUID patternViewId, @RequestBody Pattern pattern) {
+ public ResponseEntity> addPatternToPatternView(@PathVariable UUID patternViewId, @RequestBody Pattern pattern) {
+ Object renderedContent = patternRenderService.renderContent(pattern, null);
+ if (renderedContent != null){
+ pattern.setRenderedContent(renderedContent);
+ } else {
+ pattern.setRenderedContent(pattern.getContent());
+ }
this.patternViewService.addPatternToPatternView(patternViewId, pattern.getId());
return ResponseEntity.created(linkTo(methodOn(PatternController.class)
.getPatternOfPatternViewById(patternViewId, pattern.getId())).toUri()).build();
@@ -345,7 +353,12 @@ ResponseEntity> addPatternToPatternLanguage(@PathVariable UUID patternLanguage
if (null == pattern.getUri()) {
pattern.setUri(patternLanguage.getUri() + '/' + CaseUtils.toCamelCase(pattern.getName(), false));
}
-
+ Object renderedContent = patternRenderService.renderContent(pattern, null);
+ if (renderedContent != null){
+ pattern.setRenderedContent(renderedContent);
+ } else {
+ pattern.setRenderedContent(pattern.getContent());
+ }
pattern = this.patternLanguageService.createPatternAndAddToPatternLanguage(patternLanguageId, pattern);
return ResponseEntity.created(linkTo(methodOn(PatternController.class)
@@ -367,6 +380,13 @@ EntityModel updatePatternViaPut(@PathVariable UUID patternLanguageId, @
PatternLanguage patternLanguage = this.patternLanguageService.getPatternLanguageById(patternLanguageId);
Pattern persistedVersion = this.patternService.getPatternById(patternId);
// Remark: At the moment we do not support changing name, uri of a pattern
+
+ Object renderedContent = patternRenderService.renderContent(pattern, persistedVersion);
+ if (renderedContent != null){
+ persistedVersion.setRenderedContent(renderedContent);
+ } else {
+ persistedVersion.setRenderedContent(pattern.getContent());
+ }
persistedVersion.setIconUrl(pattern.getIconUrl());
persistedVersion.setContent(pattern.getContent());
@@ -374,6 +394,7 @@ EntityModel updatePatternViaPut(@PathVariable UUID patternLanguageId, @
return new EntityModel<>(pattern,
linkTo(methodOn(PatternController.class).getPatternOfPatternLanguageById(patternLanguageId, patternId)).withSelfRel(),
linkTo(methodOn(PatternController.class).getPatternContentOfPattern(patternLanguageId, patternId)).withRel("content"),
+ linkTo(methodOn(PatternController.class).getPatternRenderedContentOfPattern(patternLanguageId, patternId)).withRel("renderedContent"),
linkTo(methodOn(PatternLanguageController.class).getPatternLanguageById(patternLanguageId)).withRel("patternLanguage"));
}
@@ -400,6 +421,27 @@ EntityModel