diff --git a/README.md b/README.md index 2273ce9..8e22e56 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,15 @@ CREATE TABLE chahoutan_post_anchors CONSTRAINT fk66omt1yfcmam8rn9s3f5qh6pq FOREIGN KEY (revision_id) REFERENCES chahoutan_revisions (id) ); +CREATE TABLE chahoutan_post_footnotes +( + revision_id uuid NOT NULL, + footnote text NOT NULL, + footnote_ordinal int4 NOT NULL, + CONSTRAINT chahoutan_post_footnotes_pkey PRIMARY KEY (revision_id, footnote_ordinal), + CONSTRAINT fk7epb56mbnx4mqd2vvu5k7fv46 FOREIGN KEY (revision_id) REFERENCES chahoutan_revisions (id) +); + CREATE TABLE chahoutan_post_images ( revision_id uuid NOT NULL, diff --git a/src/main/java/org/teacon/chahoutan/entity/Revision.java b/src/main/java/org/teacon/chahoutan/entity/Revision.java index 28f80b9..c633209 100644 --- a/src/main/java/org/teacon/chahoutan/entity/Revision.java +++ b/src/main/java/org/teacon/chahoutan/entity/Revision.java @@ -47,6 +47,12 @@ public class Revision @OrderBy("uploadDate ASC") private List corrections = new ArrayList<>(); + @ElementCollection + @OrderColumn(name = "footnote_ordinal", columnDefinition = "int") + @Column(name = "footnote", columnDefinition = "text", nullable = false) + @CollectionTable(name = "chahoutan_post_footnotes", joinColumns = @JoinColumn(name = "revision_id")) + private List footnotes = new ArrayList<>(); + @ElementCollection @MapKeyColumn(name = "anchor", columnDefinition = "text") @Column(name = "link", columnDefinition = "text", nullable = false) @@ -106,6 +112,16 @@ public List getImages() return List.copyOf(this.images); } + public Map getFootnotes() + { + var footnoteCount = this.footnotes.size(); + var map = new LinkedHashMap(footnoteCount); + for (int i = 0; i < footnoteCount; ++i) { + map.put(String.format("[%d]", i + 1), this.footnotes.get(i)); + } + return Map.copyOf(map); + } + public String getRssPlainText() { var editors = this.post.getEditors(); @@ -201,6 +217,14 @@ public void setCreationTime(Instant time) this.creationTime = time.atZone(ChahoutanConfig.POST_ZONE_ID); } + public void setFootnotes(Map footnotes) { + var array = new String[footnotes.size()]; + for (var i = 0; i < array.length; ++i) { + array[i] = Objects.requireNonNull(footnotes.get(String.format("[%d]", i + 1))); + } + this.footnotes = List.of(array); + } + public void setAnchors(List anchors) { var entries = new HashMap(); diff --git a/src/main/java/org/teacon/chahoutan/network/PostRequest.java b/src/main/java/org/teacon/chahoutan/network/PostRequest.java index bc70ad2..7b6e106 100644 --- a/src/main/java/org/teacon/chahoutan/network/PostRequest.java +++ b/src/main/java/org/teacon/chahoutan/network/PostRequest.java @@ -8,6 +8,8 @@ import java.time.Instant; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; @JsonIgnoreProperties(ignoreUnknown = true) @@ -15,6 +17,7 @@ public record PostRequest(@JsonProperty(value = "id", required = true) int id, @JsonProperty(value = "text", required = true) String text, @JsonProperty(value = "editors") List editors, @JsonProperty(value = "anchors") List anchors, + @JsonProperty(value = "footnotes") Map footnotes, @JsonProperty(value = "images") List images) { private static Stream nullToEmpty(List input) @@ -22,6 +25,11 @@ private static Stream nullToEmpty(List input) return input == null ? Stream.empty() : input.stream(); } + private static Stream> nullToEmpty(Map input) + { + return input == null ? Stream.empty() : input.entrySet().stream(); + } + private static String normalize(String text) { return String.join("\u00a0", text.split("\\s")); @@ -40,6 +48,9 @@ public Post toPost(ImageRepository repo) revision.setText(normalize(this.text)); revision.setCreationTime(Instant.now()); revision.setAnchors(nullToEmpty(this.anchors).map(PostRequest::normalize).toList()); + revision.setFootnotes(nullToEmpty(this.footnotes) + .filter(e -> e.getKey().startsWith("[") && e.getKey().endsWith("]")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); revision.setImages(nullToEmpty(this.images).map(request -> request.toImage(repo)).toList()); return post; diff --git a/src/main/java/org/teacon/chahoutan/network/PostResponse.java b/src/main/java/org/teacon/chahoutan/network/PostResponse.java index 07c81d2..0f6178e 100644 --- a/src/main/java/org/teacon/chahoutan/network/PostResponse.java +++ b/src/main/java/org/teacon/chahoutan/network/PostResponse.java @@ -9,6 +9,7 @@ import java.net.URI; import java.time.OffsetDateTime; import java.util.List; +import java.util.Map; import java.util.UUID; @JsonInclude(value = JsonInclude.Include.NON_NULL) @@ -23,6 +24,7 @@ public record PostResponse(@JsonProperty(value = "id") int id, @JsonProperty(value = "anchors") List anchors, @JsonProperty(value = "anchor_urls") List anchorUrls, @JsonProperty(value = "images") List postImages, + @JsonProperty(value = "footnotes") Map footnotes, @JsonProperty(value = "publish_time") OffsetDateTime publishTime) { public static PostResponse from(Post post) @@ -30,6 +32,7 @@ public static PostResponse from(Post post) var revision = post.getRevision(); var revisionName = revision.getTitle(); var publishTime = post.getPublishTime(); + var footnotes = revision.getFootnotes(); var urlPrefix = URI.create(ChahoutanConfig.BACKEND_URL_PREFIX); var url = urlPrefix.resolve("v1/posts/" + post.getId()); var revisionUrl = urlPrefix.resolve("v1/posts/" + revision.getId()); @@ -37,7 +40,7 @@ public static PostResponse from(Post post) var editors = post.getEditors().stream().sorted().toList(); var images = revision.getImages().stream().map(ImageResponse::from).toList(); return new PostResponse(post.getId(), url, type, revisionName, revision.getText(), revision.getId(), - revisionUrl, editors, revision.getAnchors(), revision.getAnchorUrls(), images, publishTime); + revisionUrl, editors, revision.getAnchors(), revision.getAnchorUrls(), images, footnotes, publishTime); } public static PostResponse from(Revision revision) @@ -45,6 +48,7 @@ public static PostResponse from(Revision revision) var post = revision.getPost(); var revisionName = revision.getTitle(); var publishTime = post.getPublishTime(); + var footnotes = revision.getFootnotes(); var isPost = post.getRevision() != null && post.getRevision().getId().equals(revision.getId()); var urlPrefix = URI.create(ChahoutanConfig.BACKEND_URL_PREFIX); var url = isPost ? urlPrefix.resolve("v1/posts/" + post.getId()) : null; @@ -53,6 +57,6 @@ public static PostResponse from(Revision revision) var editors = isPost ? post.getEditors().stream().sorted().toList() : null; var images = revision.getImages().stream().map(ImageResponse::from).toList(); return new PostResponse(post.getId(), url, type, revisionName, revision.getText(), revision.getId(), - revisionUrl, editors, revision.getAnchors(), revision.getAnchorUrls(), images, publishTime); + revisionUrl, editors, revision.getAnchors(), revision.getAnchorUrls(), images, footnotes, publishTime); } }