summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2022-05-26 08:05:00 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2022-06-04 16:24:24 +0200
commit0351a8fbbac6cfa671128513605288fb940ce94b (patch)
tree1613514bf7f539f6e55176e58c67885fcc91b141 /src/main/java
parent14e59440fd5f03d046e4e329eec73560450f1467 (diff)
KB66 Add comment box.
Change-Id: I9bf140ded85045b09997145ed2a9fb421fedc7d4
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java8
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java6
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java8
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java14
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java54
-rw-r--r--src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java15
-rw-r--r--src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java51
7 files changed, 129 insertions, 27 deletions
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java
index c30f3df..06ea299 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java
@@ -1,7 +1,6 @@
package eu.mulk.mulkcms2.benki.bookmarks;
import eu.mulk.mulkcms2.benki.posts.PostText;
-import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;
import javax.annotation.CheckForNull;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -22,10 +21,7 @@ public class BookmarkText extends PostText<Bookmark> {
@Transient
@CheckForNull
- protected String computeDescriptionHtml() {
- if (description == null) {
- return null;
- }
- return new MarkdownConverter().htmlify(description);
+ protected String getDescriptionMarkup() {
+ return description;
}
}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java
index 63f4791..7f6ed3b 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java
@@ -21,11 +21,7 @@ public class LazychatMessage extends Post<LazychatMessageText> {
joinColumns = {@JoinColumn(name = "referrer")},
inverseJoinColumns = {@JoinColumn(name = "referee")})
@JsonbTransient
- public Collection<LazychatMessage> referees;
-
- @ManyToMany(mappedBy = "referees")
- @JsonbTransient
- public Collection<LazychatMessage> referrers;
+ public Collection<Post<?>> referees;
@CheckForNull
@Override
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java
index 1a60877..72bb983 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java
@@ -1,7 +1,6 @@
package eu.mulk.mulkcms2.benki.lazychat;
import eu.mulk.mulkcms2.benki.posts.PostText;
-import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;
import javax.annotation.CheckForNull;
import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.Column;
@@ -19,10 +18,7 @@ public class LazychatMessageText extends PostText<LazychatMessage> {
@CheckForNull
@Override
@JsonbTransient
- protected String computeDescriptionHtml() {
- if (content == null) {
- return null;
- }
- return new MarkdownConverter().htmlify(content);
+ protected String getDescriptionMarkup() {
+ return content;
}
}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
index 2c56285..d3e7712 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
@@ -109,6 +109,10 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
@JsonbTransient
public Set<Role> targets;
+ @ManyToMany(mappedBy = "referees")
+ @JsonbTransient
+ public Collection<LazychatMessage> referrers;
+
@OneToMany(
mappedBy = "post",
fetch = FetchType.LAZY,
@@ -389,6 +393,16 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {
}
}
+ public Collection<LazychatMessage> getComments() {
+ return referrers.stream()
+ .filter(l -> l.scope == Scope.comment)
+ .sorted(
+ Comparator.comparing(
+ (LazychatMessage l) -> Objects.requireNonNullElse(l.date, OffsetDateTime.MIN))
+ .reversed())
+ .toList();
+ }
+
public enum Visibility {
PUBLIC,
SEMIPRIVATE,
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
index 24a564b..5a38262 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
@@ -4,6 +4,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.ws.rs.core.MediaType.APPLICATION_ATOM_XML;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.TEXT_HTML;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import com.blazebit.persistence.CriteriaBuilderFactory;
import com.rometools.rome.feed.atom.Content;
@@ -15,8 +16,10 @@ import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.WireFeedOutput;
import eu.mulk.mulkcms2.benki.accesscontrol.PageKey;
import eu.mulk.mulkcms2.benki.accesscontrol.Role;
+import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
import eu.mulk.mulkcms2.benki.login.LoginRoles;
import eu.mulk.mulkcms2.benki.posts.Post.PostPage;
+import eu.mulk.mulkcms2.benki.posts.Post.Scope;
import eu.mulk.mulkcms2.benki.users.User;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateExtension;
@@ -25,6 +28,7 @@ import io.quarkus.security.identity.SecurityIdentity;
import java.math.BigInteger;
import java.net.URI;
import java.net.URLEncoder;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
@@ -49,14 +53,20 @@ import javax.json.spi.JsonProvider;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
+import javax.validation.constraints.NotEmpty;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.Session;
@@ -75,6 +85,8 @@ public abstract class PostResource {
private static final DateTimeFormatter humanDateFormatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
+ private static final String hashcashDigestAlgorithm = "SHA-256";
+
private static final int pageKeyBytes = 32;
private static final int AUTOTITLE_WORDS = 10;
@@ -283,6 +295,48 @@ public abstract class PostResource {
return makeFeed(pageKey, ownerName, owner);
}
+ @POST
+ @Produces(TEXT_PLAIN)
+ @Path("{id}/comments")
+ @Transactional
+ public Response postComment(
+ @PathParam("id") int postId,
+ @FormParam("message") @NotEmpty String message,
+ @FormParam("hashcash-salt") long hashcashSalt)
+ throws NoSuchAlgorithmException {
+ var hashcashDigest = MessageDigest.getInstance(hashcashDigestAlgorithm);
+ hashcashDigest.update("Hashcash-Salt: ".getBytes(UTF_8));
+ hashcashDigest.update(String.valueOf(hashcashSalt).getBytes(UTF_8));
+ hashcashDigest.update("\n\n".getBytes(UTF_8));
+
+ for (byte b : message.getBytes(UTF_8)) {
+ if (b == '\r') {
+ // Skip CR characters. The JavaScript side does not include them in its computation.
+ continue;
+ }
+ hashcashDigest.update(b);
+ }
+ var hashcash = hashcashDigest.digest();
+
+ if (hashcash[0] != 0 || hashcash[1] != 0) {
+ throw new BadRequestException(
+ "invalid hashcash",
+ Response.status(Status.BAD_REQUEST).entity("invalid hashcash").build());
+ }
+
+ Post<?> post = Post.findById(postId);
+
+ var comment = new LazychatMessage();
+ comment.date = OffsetDateTime.now();
+ comment.scope = Scope.comment;
+ comment.referees = List.of(post);
+ comment.setContent(message);
+ assignPostTargets(post.getVisibility(), post.owner, comment);
+ comment.persist();
+
+ return Response.seeOther(UriBuilder.fromUri("/posts/{id}").build(postId)).build();
+ }
+
private String makeFeed(
@CheckForNull BigInteger pageKey, @CheckForNull String ownerName, @CheckForNull User owner)
throws FeedException {
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java
index 11ac98a..80971b1 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java
@@ -1,6 +1,9 @@
package eu.mulk.mulkcms2.benki.posts;
import com.vladmihalcea.hibernate.type.search.PostgreSQLTSVectorType;
+import eu.mulk.mulkcms2.benki.posts.Post.Scope;
+import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;
+import eu.mulk.mulkcms2.common.markdown.MarkdownConverter.Mode;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import javax.annotation.CheckForNull;
import javax.json.bind.annotation.JsonbTransient;
@@ -69,5 +72,15 @@ public abstract class PostText<OwningPost extends Post<?>> extends PanacheEntity
}
@CheckForNull
- protected abstract String computeDescriptionHtml();
+ protected abstract String getDescriptionMarkup();
+
+ @CheckForNull
+ private String computeDescriptionHtml() {
+ var markup = getDescriptionMarkup();
+ if (markup == null) {
+ return null;
+ }
+ return new MarkdownConverter(post.scope == Scope.top_level ? Mode.POST : Mode.COMMENT)
+ .htmlify(markup);
+ }
}
diff --git a/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java b/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java
index 859fd71..2a144c5 100644
--- a/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java
+++ b/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java
@@ -11,18 +11,22 @@ import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;
import java.util.Arrays;
-import javax.enterprise.context.ApplicationScoped;
import org.jsoup.Jsoup;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Safelist;
-@ApplicationScoped
public class MarkdownConverter {
+ public enum Mode {
+ POST,
+ COMMENT,
+ }
+
private final Parser parser;
private final HtmlRenderer renderer;
+ private final Mode mode;
- public MarkdownConverter() {
+ public MarkdownConverter(Mode mode) {
var options = new MutableDataSet();
options.set(
Parser.EXTENSIONS,
@@ -42,6 +46,7 @@ public class MarkdownConverter {
options.set(TypographicExtension.ENABLE_QUOTES, true);
options.set(FootnoteExtension.FOOTNOTE_BACK_REF_STRING, "");
+ this.mode = mode;
this.parser = Parser.builder(options).build();
this.renderer = HtmlRenderer.builder(options).build();
}
@@ -49,14 +54,42 @@ public class MarkdownConverter {
public String htmlify(String markdown) {
var parsedDocument = parser.parse(markdown);
var uncleanHtml = renderer.render(parsedDocument);
- var cleaner =
- new Cleaner(
- Safelist.relaxed()
- .addTags("abbr", "acronym")
- .addAttributes("abbr", "title")
- .addAttributes("acronym", "title"));
+ var cleaner = makeCleaner();
var cleanedDocument = cleaner.clean(Jsoup.parseBodyFragment(uncleanHtml));
cleanedDocument.select("table").addClass("pure-table").addClass("pure-table-horizontal");
return cleanedDocument.body().html();
}
+
+ private Cleaner makeCleaner() {
+ var safelist =
+ switch (mode) {
+ case POST -> Safelist.relaxed()
+ .addTags("abbr", "acronym")
+ .addAttributes("abbr", "title")
+ .addAttributes("acronym", "title");
+ case COMMENT -> Safelist.simpleText()
+ .addTags(
+ "p",
+ "blockquote",
+ "cite",
+ "code",
+ "pre",
+ "dd",
+ "dl",
+ "dt",
+ "s",
+ "sub",
+ "sup",
+ "ol",
+ "ul",
+ "li",
+ "abbr",
+ "acronym",
+ "ins",
+ "del")
+ .addAttributes("abbr", "title")
+ .addAttributes("acronym", "title");
+ };
+ return new Cleaner(safelist);
+ }
}