Book Marx: Implement basic viewer.

Change-Id: I5a878ca82d8489c6a87c86f66a49a085f168f86c
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java
index 783a882..8985948 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java
@@ -1,11 +1,13 @@
 package eu.mulk.mulkcms2.benki.bookmarks;
 
 import eu.mulk.mulkcms2.benki.generic.Post;
-import java.util.Collection;
+import java.util.Set;
+import javax.persistence.CollectionTable;
 import javax.persistence.Column;
+import javax.persistence.ElementCollection;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
-import javax.persistence.OneToMany;
+import javax.persistence.JoinColumn;
 import javax.persistence.Table;
 
 @Entity
@@ -21,6 +23,11 @@
   @Column(name = "description", nullable = true, length = -1)
   public String description;
 
-  @OneToMany(mappedBy = "bookmark", fetch = FetchType.LAZY)
-  public Collection<BookmarkTag> tags;
+  @ElementCollection(fetch = FetchType.LAZY)
+  @CollectionTable(
+      name = "bookmark_tags",
+      schema = "benki",
+      joinColumns = @JoinColumn(name = "bookmark"))
+  @Column(name = "tag")
+  public Set<String> tags;
 }
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java
new file mode 100644
index 0000000..45c7087
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java
@@ -0,0 +1,77 @@
+package eu.mulk.mulkcms2.benki.bookmarks;
+
+import static javax.ws.rs.core.MediaType.TEXT_HTML;
+
+import eu.mulk.mulkcms2.benki.accesscontrol.Role;
+import eu.mulk.mulkcms2.benki.users.User;
+import io.quarkus.panache.common.Sort;
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateExtension;
+import io.quarkus.qute.TemplateInstance;
+import io.quarkus.qute.api.ResourcePath;
+import io.quarkus.security.identity.SecurityIdentity;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.temporal.TemporalAccessor;
+import java.util.List;
+import javax.inject.Inject;
+import javax.json.spi.JsonProvider;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import org.jboss.logging.Logger;
+
+@Path("/bookmarks")
+public class BookmarkResource {
+
+  private static Logger log = Logger.getLogger(BookmarkResource.class);
+
+  private static DateTimeFormatter htmlDateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+  private static DateTimeFormatter humanDateFormatter =
+      DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
+
+  private static JsonProvider jsonProvider = JsonProvider.provider();
+
+  @ResourcePath("benki/bookmarks/bookmarkList.html")
+  @Inject
+  Template bookmarkList;
+
+  @Inject SecurityIdentity identity;
+
+  @GET
+  @Produces(TEXT_HTML)
+  public TemplateInstance getPage() {
+    List<Bookmark> bookmarks;
+    if (identity.isAnonymous()) {
+      Role world = Role.find("from Role r join r.tags tag where tag = 'world'").singleResult();
+      bookmarks =
+          Bookmark.find(
+                  "select bm from Bookmark bm join bm.targets target left join fetch bm.owner where target = ?1",
+                  Sort.by("date").descending(),
+                  world)
+              .list();
+    } else {
+      var userName = identity.getPrincipal().getName();
+      User user =
+          User.find("from BenkiUser u join u.nicknames n where ?1 = n", userName).singleResult();
+      bookmarks =
+          Bookmark.find(
+                  "select bm from BenkiUser u inner join u.visibleBookmarks bm left join fetch bm.owner where u.id = ?1",
+                  Sort.by("date").descending(),
+                  user.id)
+              .list();
+    }
+    return bookmarkList.data("bookmarks", bookmarks);
+  }
+
+  @TemplateExtension
+  static String humanDateTime(TemporalAccessor x) {
+    return humanDateFormatter.format(x);
+  }
+
+  @TemplateExtension
+  static String htmlDateTime(TemporalAccessor x) {
+    return htmlDateFormatter.format(x);
+  }
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTag.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTag.java
deleted file mode 100644
index ce2d1c6..0000000
--- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTag.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package eu.mulk.mulkcms2.benki.bookmarks;
-
-import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.Id;
-import javax.persistence.IdClass;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.Table;
-
-@Entity
-@Table(name = "bookmark_tags", schema = "benki")
-@IdClass(BookmarkTagPK.class)
-public class BookmarkTag extends PanacheEntityBase {
-
-  @Id
-  @Column(name = "bookmark", nullable = false)
-  public int bookmarkId;
-
-  @Id
-  @Column(name = "tag", nullable = false, length = -1)
-  public String tag;
-
-  @ManyToOne(fetch = FetchType.LAZY)
-  @JoinColumn(name = "bookmark", referencedColumnName = "id", nullable = false)
-  public Bookmark bookmark;
-}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTagPK.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTagPK.java
deleted file mode 100644
index e8abb3b..0000000
--- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTagPK.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package eu.mulk.mulkcms2.benki.bookmarks;
-
-import java.io.Serializable;
-import javax.persistence.Column;
-import javax.persistence.Id;
-
-public class BookmarkTagPK implements Serializable {
-
-  private int bookmarkId;
-  private String tag;
-
-  @Column(name = "bookmark", nullable = false)
-  @Id
-  public int getBookmarkId() {
-    return bookmarkId;
-  }
-
-  public void setBookmarkId(int bookmarkId) {
-    this.bookmarkId = bookmarkId;
-  }
-
-  @Column(name = "tag", nullable = false, length = -1)
-  @Id
-  public String getTag() {
-    return tag;
-  }
-
-  public void setTag(String tag) {
-    this.tag = tag;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-
-    BookmarkTagPK that = (BookmarkTagPK) o;
-
-    if (bookmarkId != that.bookmarkId) {
-      return false;
-    }
-    if (tag != null ? !tag.equals(that.tag) : that.tag != null) {
-      return false;
-    }
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = bookmarkId;
-    result = 31 * result + (tag != null ? tag.hashCode() : 0);
-    return result;
-  }
-}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/generic/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/generic/Post.java
index 596a6f7..b4b4222 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/generic/Post.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/generic/Post.java
@@ -1,5 +1,6 @@
 package eu.mulk.mulkcms2.benki.generic;
 
+import eu.mulk.mulkcms2.benki.accesscontrol.Role;
 import eu.mulk.mulkcms2.benki.users.User;
 import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
 import java.time.OffsetDateTime;
@@ -48,4 +49,12 @@
       joinColumns = @JoinColumn(name = "message"),
       inverseJoinColumns = @JoinColumn(name = "user"))
   public Set<User> visibleTo;
+
+  @ManyToMany(fetch = FetchType.LAZY)
+  @JoinTable(
+      name = "post_targets",
+      schema = "benki",
+      joinColumns = @JoinColumn(name = "message"),
+      inverseJoinColumns = @JoinColumn(name = "target"))
+  public Set<Role> targets;
 }
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 2a0d5f4..31c13dd 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java
@@ -113,6 +113,12 @@
   @ManyToMany(mappedBy = "visibleTo", fetch = FetchType.LAZY)
   public Set<Post> visiblePosts;
 
+  @ManyToMany(mappedBy = "visibleTo", fetch = FetchType.LAZY)
+  public Set<Bookmark> visibleBookmarks;
+
+  @ManyToMany(mappedBy = "visibleTo", fetch = FetchType.LAZY)
+  public Set<LazychatMessage> visibleLazychatMessages;
+
   @ManyToMany(fetch = FetchType.LAZY)
   @JoinTable(
       name = "effective_user_roles",
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiResource.java b/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiResource.java
index 090dafd..74e3d1b 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiResource.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiResource.java
@@ -30,9 +30,7 @@
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import javax.ws.rs.RedirectionException;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
 import org.jboss.logging.Logger;
 import org.jsoup.Jsoup;
 import org.jsoup.safety.Whitelist;
diff --git a/src/main/resources/META-INF/resources/cms2/base.css b/src/main/resources/META-INF/resources/cms2/base.css
index 6e3b1b6..e09262f 100644
--- a/src/main/resources/META-INF/resources/cms2/base.css
+++ b/src/main/resources/META-INF/resources/cms2/base.css
@@ -123,4 +123,8 @@
 
   padding: 0.5em 0.5em;
   border-top: lightgray solid 1px;
+}
+
+h1.bookmark-title {
+  font-size: 1em;
 }
\ No newline at end of file
diff --git a/src/main/resources/templates/benki/bookmarks/bookmarkList.html b/src/main/resources/templates/benki/bookmarks/bookmarkList.html
new file mode 100644
index 0000000..4ad97fd
--- /dev/null
+++ b/src/main/resources/templates/benki/bookmarks/bookmarkList.html
@@ -0,0 +1,33 @@
+{@java.util.List<eu.mulk.mulkcms2.benki.bookmarks.Bookmark> bookmarks}
+
+{#include base.html}
+
+{#title}Benki Bookmarks{/title}
+{#siteSection}Bookmarks{/siteSection}
+{#bookmarksClass}this-page{/bookmarksClass}
+
+{#head}{/head}
+
+{#body}
+
+{#for bookmark in bookmarks}
+  {#with bookmark}
+    <article class="bookmark">
+      <header>
+        <a href="{uri}"><h1 class="bookmark-title">{title}</h1></a>
+        <div>
+          <time datetime="{date.htmlDateTime}">{date.humanDateTime}</time>
+          <span class="bookmark-owner">{owner.firstName} {owner.lastName}</span>
+        </div>
+      </header>
+
+      <section class="bookmark-description">
+        {description}
+      </section>
+    </article>
+  {/with}
+{/for}
+
+{/body}
+
+{/include}