From 06e6c81c77f8098693473e49c11557820541dd15 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Mon, 13 Apr 2020 17:01:35 +0200 Subject: Lazy Chat: Implement editing of messages. Change-Id: I291201da1fbc7c2b6563f0837f7ce3e2f7f8555c --- .../eu/mulk/mulkcms2/benki/accesscontrol/Role.java | 18 +++++ .../mulkcms2/benki/bookmarks/BookmarkResource.java | 21 ++---- .../mulkcms2/benki/lazychat/LazychatMessage.java | 4 ++ .../mulkcms2/benki/lazychat/LazychatResource.java | 78 ++++++++++++++++------ .../java/eu/mulk/mulkcms2/benki/posts/Post.java | 40 +++++++++++ .../eu/mulk/mulkcms2/benki/posts/PostResource.java | 30 ++++++++- .../java/eu/mulk/mulkcms2/benki/users/User.java | 6 ++ 7 files changed, 160 insertions(+), 37 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java b/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java index 6298245..349322b 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java @@ -5,6 +5,7 @@ import eu.mulk.mulkcms2.benki.users.User; import eu.mulk.mulkcms2.benki.users.UserRole; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import java.util.Collection; +import java.util.Objects; import java.util.Set; import javax.persistence.CollectionTable; import javax.persistence.Column; @@ -78,4 +79,21 @@ public class Role extends PanacheEntityBase { public static Role getWorld() { return find("from Role r join r.tags tag where tag = 'world'").singleResult(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Role)) { + return false; + } + Role role = (Role) o; + return Objects.equals(id, role.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java index f2e3067..cb4c20f 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java @@ -3,10 +3,9 @@ package eu.mulk.mulkcms2.benki.bookmarks; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.TEXT_HTML; -import eu.mulk.mulkcms2.benki.accesscontrol.Role; +import eu.mulk.mulkcms2.benki.posts.Post; import eu.mulk.mulkcms2.benki.posts.PostFilter; import eu.mulk.mulkcms2.benki.posts.PostResource; -import eu.mulk.mulkcms2.benki.users.User; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; import io.quarkus.qute.api.ResourcePath; @@ -21,9 +20,6 @@ import javax.inject.Inject; import javax.json.JsonObject; import javax.transaction.Transactional; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.ws.rs.BadRequestException; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -51,12 +47,10 @@ public class BookmarkResource extends PostResource { @FormParam("uri") URI uri, @FormParam("title") @NotEmpty String title, @FormParam("description") String description, - @FormParam("visibility") @NotNull @Pattern(regexp = "public|semiprivate|private") - String visibility) + @FormParam("visibility") Post.Visibility visibility) throws URISyntaxException { - var userName = identity.getPrincipal().getName(); - var user = User.findByNickname(userName); + var user = getCurrentUser(); var bookmark = new Bookmark(); bookmark.uri = uri.toString(); @@ -66,14 +60,7 @@ public class BookmarkResource extends PostResource { bookmark.owner = user; bookmark.date = OffsetDateTime.now(); - if (visibility.equals("public")) { - Role world = Role.find("from Role r join r.tags tag where tag = 'world'").singleResult(); - bookmark.targets = Set.of(world); - } else if (visibility.equals("semiprivate")) { - bookmark.targets = Set.copyOf(user.defaultTargets); - } else if (!visibility.equals("private")) { - throw new BadRequestException(String.format("invalid visibility “%s”", visibility)); - } + assignPostTargets(visibility, user, bookmark); bookmark.persistAndFlush(); 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 918cad7..1e92c38 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java @@ -4,6 +4,7 @@ import eu.mulk.mulkcms2.benki.posts.Post; import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; import java.util.Collection; import javax.annotation.CheckForNull; +import javax.json.bind.annotation.JsonbTransient; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -22,9 +23,11 @@ public class LazychatMessage extends Post { public String format; @OneToMany(mappedBy = "referrer", fetch = FetchType.LAZY) + @JsonbTransient public Collection references; @Transient + @JsonbTransient public String getContentHtml() { return new MarkdownConverter().htmlify(content); } @@ -43,6 +46,7 @@ public class LazychatMessage extends Post { @CheckForNull @Override + @JsonbTransient public String getDescriptionHtml() { return getContentHtml(); } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java index 8a4d2a3..fd672f8 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java @@ -1,21 +1,24 @@ package eu.mulk.mulkcms2.benki.lazychat; -import eu.mulk.mulkcms2.benki.accesscontrol.Role; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import eu.mulk.mulkcms2.benki.posts.Post; import eu.mulk.mulkcms2.benki.posts.PostFilter; import eu.mulk.mulkcms2.benki.posts.PostResource; -import eu.mulk.mulkcms2.benki.users.User; import io.quarkus.security.Authenticated; import java.net.URI; import java.net.URISyntaxException; import java.time.OffsetDateTime; -import java.util.Set; +import java.util.Objects; import javax.transaction.Transactional; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.ws.rs.BadRequestException; +import javax.ws.rs.ForbiddenException; import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; import javax.ws.rs.core.Response; @Path("/lazychat") @@ -29,13 +32,10 @@ public class LazychatResource extends PostResource { @Transactional @Authenticated public Response postMessage( - @FormParam("text") String text, - @FormParam("visibility") @NotNull @Pattern(regexp = "public|semiprivate|private") - String visibility) + @FormParam("text") String text, @FormParam("visibility") Post.Visibility visibility) throws URISyntaxException { - var userName = identity.getPrincipal().getName(); - var user = User.findByNickname(userName); + var user = getCurrentUser(); var message = new LazychatMessage(); message.content = text; @@ -43,17 +43,57 @@ public class LazychatResource extends PostResource { message.owner = user; message.date = OffsetDateTime.now(); - if (visibility.equals("public")) { - Role world = Role.find("from Role r join r.tags tag where tag = 'world'").singleResult(); - message.targets = Set.of(world); - } else if (visibility.equals("semiprivate")) { - message.targets = Set.copyOf(user.defaultTargets); - } else if (!visibility.equals("private")) { - throw new BadRequestException(String.format("invalid visibility “%s”", visibility)); - } + assignPostTargets(visibility, user, message); message.persistAndFlush(); return Response.seeOther(new URI("/lazychat")).build(); } + + @POST + @Transactional + @Authenticated + @Path("/p/{id}/edit") + public Response patchMessage( + @PathParam("id") int id, + @FormParam("text") String text, + @FormParam("visibility") Post.Visibility visibility) + throws URISyntaxException { + + var user = getCurrentUser(); + + var message = getSession().byId(LazychatMessage.class).load(id); + + if (message == null) { + throw new NotFoundException(); + } + + if (!Objects.equals(message.owner.id, user.id)) { + throw new ForbiddenException(); + } + + message.content = text; + message.format = "markdown"; + + assignPostTargets(visibility, user, message); + + return Response.seeOther(new URI("/lazychat")).build(); + } + + @GET + @Transactional + @Produces(APPLICATION_JSON) + @Path("/p/{id}") + public LazychatMessage getMessage(@PathParam("id") int id) { + + var user = getCurrentUser(); + + var message = getSession().byId(LazychatMessage.class).load(id); + + if (!user.canSee(message)) { + throw new ForbiddenException(); + } + + return message; + } } 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 20aec05..356461c 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import javax.annotation.CheckForNull; +import javax.json.bind.annotation.JsonbTransient; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -57,6 +58,7 @@ public abstract class Post extends PanacheEntityBase { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner", referencedColumnName = "id") + @JsonbTransient public User owner; @ManyToMany(fetch = FetchType.LAZY) @@ -65,6 +67,7 @@ public abstract class Post extends PanacheEntityBase { schema = "benki", joinColumns = @JoinColumn(name = "message"), inverseJoinColumns = @JoinColumn(name = "user")) + @JsonbTransient public Set visibleTo; @ManyToMany(fetch = FetchType.LAZY) @@ -73,6 +76,7 @@ public abstract class Post extends PanacheEntityBase { schema = "benki", joinColumns = @JoinColumn(name = "message"), inverseJoinColumns = @JoinColumn(name = "target")) + @JsonbTransient public Set targets; public abstract boolean isBookmark(); @@ -88,6 +92,18 @@ public abstract class Post extends PanacheEntityBase { @CheckForNull public abstract String getUri(); + public Visibility getVisibility() { + if (targets.isEmpty()) { + return Visibility.PRIVATE; + } else if (targets.contains(Role.getWorld())) { + return Visibility.PUBLIC; + } else { + // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here. + // Otherwise the actual visibility is DISCRETIONARY. + return Visibility.SEMIPRIVATE; + } + } + protected static CriteriaQuery queryViewable( Class entityClass, SecurityIdentity readerIdentity, @@ -236,4 +252,28 @@ public abstract class Post extends PanacheEntityBase { return new PostPage(prevCursor, cursor, nextCursor, forwardResults); } + + public enum Visibility { + PUBLIC, + SEMIPRIVATE, + DISCRETIONARY, + PRIVATE, + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Post)) { + return false; + } + Post post = (Post) o; + return Objects.equals(id, post.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } } 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 fbe6bf7..a691490 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java @@ -10,6 +10,7 @@ import com.rometools.rome.feed.atom.Link; import com.rometools.rome.feed.synd.SyndPersonImpl; import com.rometools.rome.io.FeedException; import com.rometools.rome.io.WireFeedOutput; +import eu.mulk.mulkcms2.benki.accesscontrol.Role; import eu.mulk.mulkcms2.benki.users.User; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateExtension; @@ -26,6 +27,7 @@ import java.time.temporal.TemporalAccessor; import java.util.Comparator; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -33,6 +35,7 @@ import javax.inject.Inject; import javax.json.spi.JsonProvider; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.ws.rs.BadRequestException; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -70,7 +73,7 @@ public abstract class PostResource { @ConfigProperty(name = "mulkcms.tag-base") String tagBase; - @PersistenceContext EntityManager entityManager; + @PersistenceContext protected EntityManager entityManager; private final PostFilter postFilter; private final String pageTitle; @@ -256,4 +259,29 @@ public abstract class PostResource { throw new IllegalStateException(); } } + + protected Session getSession() { + return entityManager.unwrap(Session.class); + } + + protected static void assignPostTargets(Post.Visibility visibility, User user, Post post) { + switch (visibility) { + case PUBLIC: + post.targets = Set.of(Role.getWorld()); + break; + case SEMIPRIVATE: + post.targets = Set.copyOf(user.defaultTargets); + break; + case PRIVATE: + post.targets = Set.of(); + break; + default: + throw new BadRequestException(String.format("invalid visibility “%s”", visibility)); + } + } + + protected User getCurrentUser() { + var userName = identity.getPrincipal().getName(); + return User.findByNickname(userName); + } } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java index 5879046..ab89baa 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java @@ -5,6 +5,7 @@ import eu.mulk.mulkcms2.benki.accesscontrol.Role; import eu.mulk.mulkcms2.benki.bookmarks.Bookmark; import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage; import eu.mulk.mulkcms2.benki.posts.Post; +import eu.mulk.mulkcms2.benki.posts.Post.Visibility; import eu.mulk.mulkcms2.benki.wiki.WikiPageRevision; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import java.util.Collection; @@ -141,4 +142,9 @@ public class User extends PanacheEntityBase { public static User findByNickname(String nickname) { return User.find("from BenkiUser u join u.nicknames n where ?1 = n", nickname).singleResult(); } + + public boolean canSee(Post message) { + // FIXME: Make this more efficient. + return message.getVisibility() == Visibility.PUBLIC || visiblePosts.contains(message); + } } -- cgit v1.2.3