diff options
12 files changed, 316 insertions, 330 deletions
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 f35fc6c..6298245 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java @@ -1,6 +1,6 @@  package eu.mulk.mulkcms2.benki.accesscontrol; -import eu.mulk.mulkcms2.benki.generic.PostTarget; +import eu.mulk.mulkcms2.benki.posts.PostTarget;  import eu.mulk.mulkcms2.benki.users.User;  import eu.mulk.mulkcms2.benki.users.UserRole;  import io.quarkus.hibernate.orm.panache.PanacheEntityBase; 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 ea62af3..736740a 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java @@ -1,6 +1,6 @@  package eu.mulk.mulkcms2.benki.bookmarks; -import eu.mulk.mulkcms2.benki.generic.Post; +import eu.mulk.mulkcms2.benki.posts.Post;  import eu.mulk.mulkcms2.benki.users.User;  import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;  import io.quarkus.security.identity.SecurityIdentity; 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 485a96e..f2e3067 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java @@ -1,45 +1,24 @@  package eu.mulk.mulkcms2.benki.bookmarks; -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 com.rometools.rome.feed.atom.Content; -import com.rometools.rome.feed.atom.Entry; -import com.rometools.rome.feed.atom.Feed; -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.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.TemplateExtension;  import io.quarkus.qute.TemplateInstance;  import io.quarkus.qute.api.ResourcePath;  import io.quarkus.security.Authenticated; -import io.quarkus.security.identity.SecurityIdentity;  import java.io.IOException;  import java.net.URI;  import java.net.URISyntaxException; -import java.time.Instant;  import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -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;  import javax.inject.Inject;  import javax.json.JsonObject; -import javax.json.spi.JsonProvider; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext;  import javax.transaction.Transactional;  import javax.validation.constraints.NotEmpty;  import javax.validation.constraints.NotNull; @@ -49,195 +28,20 @@ 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.UriInfo; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.hibernate.Session; -import org.jboss.logging.Logger;  import org.jsoup.Jsoup;  @Path("/bookmarks") -public class BookmarkResource { - -  private static final Logger log = Logger.getLogger(BookmarkResource.class); - -  private static final DateTimeFormatter htmlDateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - -  private static final DateTimeFormatter humanDateFormatter = -      DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); - -  private static final JsonProvider jsonProvider = JsonProvider.provider(); - -  @ConfigProperty(name = "mulkcms.bookmarks.default-max-results") -  int defaultMaxResults; - -  @ResourcePath("benki/posts/postList.html") -  @Inject -  Template postList; +public class BookmarkResource extends PostResource {    @ResourcePath("benki/bookmarks/newBookmark.html")    @Inject    Template newBookmark; -  @Inject SecurityIdentity identity; - -  @Context UriInfo uri; - -  @Inject -  @ConfigProperty(name = "mulkcms.tag-base") -  String tagBase; - -  @PersistenceContext EntityManager entityManager; - -  @GET -  @Produces(TEXT_HTML) -  public TemplateInstance getIndex( -      @QueryParam("i") @CheckForNull Integer cursor, -      @QueryParam("n") @CheckForNull Integer maxResults) { - -    maxResults = maxResults == null ? defaultMaxResults : maxResults; - -    var session = entityManager.unwrap(Session.class); -    var q = Bookmark.findViewable(session, identity, null, cursor, maxResults); - -    return postList -        .data("posts", q.posts) -        .data("feedUri", "/bookmarks/feed") -        .data("pageTitle", "Bookmarks") -        .data("showBookmarkForm", !identity.isAnonymous()) -        .data("showLazychatForm", false) -        .data("hasPreviousPage", q.prevCursor != null) -        .data("hasNextPage", q.nextCursor != null) -        .data("previousCursor", q.prevCursor) -        .data("nextCursor", q.nextCursor) -        .data("pageSize", maxResults); -  } - -  @GET -  @Path("~{ownerName}") -  @Produces(TEXT_HTML) -  public TemplateInstance getUserIndex( -      @PathParam("ownerName") String ownerName, -      @QueryParam("i") @CheckForNull Integer cursor, -      @QueryParam("n") @CheckForNull Integer maxResults) { - -    maxResults = maxResults == null ? defaultMaxResults : maxResults; - -    var owner = User.findByNickname(ownerName); -    var session = entityManager.unwrap(Session.class); -    var q = Bookmark.findViewable(session, identity, owner, cursor, maxResults); - -    return postList -        .data("posts", q.posts) -        .data("feedUri", String.format("/bookmarks/~%s/feed", ownerName)) -        .data("pageTitle", "Bookmarks") -        .data("showBookmarkForm", !identity.isAnonymous()) -        .data("showLazychatForm", false) -        .data("hasPreviousPage", q.prevCursor != null) -        .data("hasNextPage", q.nextCursor != null) -        .data("previousCursor", q.prevCursor) -        .data("nextCursor", q.nextCursor) -        .data("pageSize", maxResults); -  } - -  @GET -  @Path("feed") -  @Produces(APPLICATION_ATOM_XML) -  public String getFeed() throws FeedException { -    return makeFeed(null, null); -  } - -  @GET -  @Path("~{ownerName}/feed") -  @Produces(APPLICATION_ATOM_XML) -  public String getUserFeed(@PathParam("ownerName") String ownerName) throws FeedException { -    var owner = User.findByNickname(ownerName); -    return makeFeed(owner, ownerName); -  } - -  private String makeFeed(@Nullable User owner, @Nullable String ownerName) throws FeedException { -    var bookmarks = Bookmark.findViewable(entityManager.unwrap(Session.class), identity, owner); -    var feed = new Feed("atom_1.0"); - -    var feedSubId = owner == null ? "" : String.format("/%d", owner.id); - -    feed.setTitle("Book Marx"); -    feed.setId( -        String.format( -            "tag:%s,2019:marx%s:%s", -            tagBase, -            feedSubId, -            identity.isAnonymous() ? "world" : identity.getPrincipal().getName())); -    feed.setUpdated( -        Date.from( -            bookmarks.stream() -                .map(x -> x.date) -                .max(Comparator.comparing(x -> x)) -                .orElse(OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)) -                .toInstant())); - -    var selfLink = new Link(); -    selfLink.setHref(uri.getRequestUri().toString()); -    selfLink.setRel("self"); -    feed.setOtherLinks(List.of(selfLink)); - -    var htmlAltLink = new Link(); -    var htmlAltPath = owner == null ? "/bookmarks" : String.format("~%s/bookmarks", ownerName); -    htmlAltLink.setHref(uri.resolve(URI.create(htmlAltPath)).toString()); -    htmlAltLink.setRel("alternate"); -    htmlAltLink.setType("text/html"); -    feed.setAlternateLinks(List.of(htmlAltLink)); - -    feed.setEntries( -        bookmarks.stream() -            .map( -                bookmark -> { -                  var entry = new Entry(); - -                  entry.setId(String.format("tag:%s,2012:/marx/%d", tagBase, bookmark.id)); -                  entry.setPublished(Date.from(bookmark.date.toInstant())); -                  entry.setUpdated(Date.from(bookmark.date.toInstant())); - -                  var author = new SyndPersonImpl(); -                  author.setName(bookmark.owner.getFirstAndLastName()); -                  entry.setAuthors(List.of(author)); - -                  var title = new Content(); -                  title.setType("text"); -                  title.setValue(bookmark.title); -                  entry.setTitleEx(title); - -                  var summary = new Content(); -                  summary.setType("html"); -                  summary.setValue(bookmark.getDescriptionHtml()); -                  entry.setSummary(summary); - -                  var link = new Link(); -                  link.setHref(bookmark.uri); -                  link.setRel("alternate"); -                  entry.setAlternateLinks(List.of(link)); - -                  return entry; -                }) -            .collect(Collectors.toUnmodifiableList())); - -    var wireFeedOutput = new WireFeedOutput(); -    return wireFeedOutput.outputString(feed); -  } - -  @GET -  @Authenticated -  @Path("new") -  @Produces(TEXT_HTML) -  public TemplateInstance getNewBookmarkForm( -      @QueryParam("uri") @CheckForNull String uri, -      @QueryParam("title") @CheckForNull String title, -      @QueryParam("description") @CheckForNull String description) { -    return newBookmark.data("uri", uri).data("title", title).data("description", description); +  public BookmarkResource() { +    super(PostFilter.BOOKMARKS_ONLY, "Bookmarks");    }    @POST @@ -277,6 +81,17 @@ public class BookmarkResource {    }    @GET +  @Authenticated +  @Path("new") +  @Produces(TEXT_HTML) +  public TemplateInstance getNewBookmarkForm( +      @QueryParam("uri") @CheckForNull String uri, +      @QueryParam("title") @CheckForNull String title, +      @QueryParam("description") @CheckForNull String description) { +    return newBookmark.data("uri", uri).data("title", title).data("description", description); +  } + +  @GET    @Path("page-info")    @Authenticated    @Produces(APPLICATION_JSON) @@ -284,14 +99,4 @@ public class BookmarkResource {      var document = Jsoup.connect(uri.toString()).get();      return jsonProvider.createObjectBuilder().add("title", document.title()).build();    } - -  @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/lazychat/LazychatMessage.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java index 4c7f6a0..5e00c60 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java @@ -1,6 +1,6 @@  package eu.mulk.mulkcms2.benki.lazychat; -import eu.mulk.mulkcms2.benki.generic.Post; +import eu.mulk.mulkcms2.benki.posts.Post;  import eu.mulk.mulkcms2.benki.users.User;  import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;  import io.quarkus.security.identity.SecurityIdentity; 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 a74692b..8a4d2a3 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java @@ -1,113 +1,28 @@  package eu.mulk.mulkcms2.benki.lazychat; -import static javax.ws.rs.core.MediaType.TEXT_HTML; -  import eu.mulk.mulkcms2.benki.accesscontrol.Role; +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.TemplateExtension; -import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.ResourcePath;  import io.quarkus.security.Authenticated; -import io.quarkus.security.identity.SecurityIdentity;  import java.net.URI;  import java.net.URISyntaxException;  import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.time.temporal.TemporalAccessor;  import java.util.Set; -import javax.annotation.CheckForNull; -import javax.inject.Inject; -import javax.json.spi.JsonProvider; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext;  import javax.transaction.Transactional;  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;  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.Response; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.hibernate.Session; -import org.jboss.logging.Logger;  @Path("/lazychat") -public class LazychatResource { - -  private static final Logger log = Logger.getLogger(LazychatResource.class); - -  private static final DateTimeFormatter htmlDateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - -  private static final DateTimeFormatter humanDateFormatter = -      DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); - -  private static final JsonProvider jsonProvider = JsonProvider.provider(); - -  @ConfigProperty(name = "mulkcms.lazychat.default-max-results") -  int defaultMaxResults; - -  @ResourcePath("benki/posts/postList.html") -  @Inject -  Template postList; - -  @Inject SecurityIdentity identity; - -  @PersistenceContext EntityManager entityManager; - -  @GET -  @Produces(TEXT_HTML) -  public TemplateInstance getIndex( -      @QueryParam("i") @CheckForNull Integer cursor, -      @QueryParam("n") @CheckForNull Integer maxResults) { - -    maxResults = maxResults == null ? defaultMaxResults : maxResults; +public class LazychatResource extends PostResource { -    var session = entityManager.unwrap(Session.class); -    var q = LazychatMessage.findViewable(session, identity, null, cursor, maxResults); - -    return postList -        .data("posts", q.posts) -        .data("pageTitle", "Lazy Chat") -        .data("showBookmarkForm", false) -        .data("showLazychatForm", !identity.isAnonymous()) -        .data("hasPreviousPage", q.prevCursor != null) -        .data("hasNextPage", q.nextCursor != null) -        .data("previousCursor", q.prevCursor) -        .data("nextCursor", q.nextCursor) -        .data("pageSize", maxResults); -  } - -  @GET -  @Path("~{ownerName}") -  @Produces(TEXT_HTML) -  public TemplateInstance getUserIndex( -      @PathParam("ownerName") String ownerName, -      @QueryParam("i") @CheckForNull Integer cursor, -      @QueryParam("n") @CheckForNull Integer maxResults) { - -    maxResults = maxResults == null ? defaultMaxResults : maxResults; - -    var owner = User.findByNickname(ownerName); -    var session = entityManager.unwrap(Session.class); -    var q = LazychatMessage.findViewable(session, identity, owner, cursor, maxResults); - -    return postList -        .data("posts", q.posts) -        .data("pageTitle", "Lazy Chat") -        .data("showBookmarkForm", false) -        .data("showLazychatForm", !identity.isAnonymous()) -        .data("hasPreviousPage", q.prevCursor != null) -        .data("hasNextPage", q.nextCursor != null) -        .data("previousCursor", q.prevCursor) -        .data("nextCursor", q.nextCursor) -        .data("pageSize", maxResults); +  public LazychatResource() { +    super(PostFilter.LAZYCHAT_MESSAGES_ONLY, "Lazy Chat");    }    @POST @@ -141,14 +56,4 @@ public class LazychatResource {      return Response.seeOther(new URI("/lazychat")).build();    } - -  @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/generic/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java index 7d75bb4..fc9ba78 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/generic/Post.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java @@ -1,4 +1,4 @@ -package eu.mulk.mulkcms2.benki.generic; +package eu.mulk.mulkcms2.benki.posts;  import eu.mulk.mulkcms2.benki.accesscontrol.Role;  import eu.mulk.mulkcms2.benki.bookmarks.Bookmark; @@ -103,9 +103,10 @@ public abstract class Post extends PanacheEntityBase {        conditions.add(cb.equal(root, user));        if (entityClass.isAssignableFrom(Bookmark.class)) {          post = (From<?, T>) root.join(User_.visibleBookmarks); -      } else { -        assert entityClass.isAssignableFrom(LazychatMessage.class) : entityClass; +      } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {          post = (From<?, T>) root.join(User_.visibleLazychatMessages); +      } else { +        post = (From<?, T>) root.join(User_.visiblePosts);        }      } @@ -153,13 +154,29 @@ public abstract class Post extends PanacheEntityBase {      }    } -  protected static <T extends Post> List<T> findViewable( -      Class<T> entityClass, Session session, SecurityIdentity viewer, @CheckForNull User owner) { -    return findViewable(entityClass, session, viewer, owner, null, null).posts; +  public static PostPage<Post> findViewable( +      PostFilter postFilter, +      Session session, +      SecurityIdentity viewer, +      @CheckForNull User owner, +      @CheckForNull Integer cursor, +      @CheckForNull Integer count) { +    Class<? extends Post> entityClass; +    switch (postFilter) { +      case BOOKMARKS_ONLY: +        entityClass = Bookmark.class; +        break; +      case LAZYCHAT_MESSAGES_ONLY: +        entityClass = LazychatMessage.class; +        break; +      default: +        entityClass = Post.class; +    } +    return findViewable(entityClass, session, viewer, owner, cursor, count);    }    protected static <T extends Post> PostPage<T> findViewable( -      Class<T> entityClass, +      Class<? extends T> entityClass,        Session session,        SecurityIdentity viewer,        @CheckForNull User owner, @@ -172,7 +189,7 @@ public abstract class Post extends PanacheEntityBase {      var cb = session.getCriteriaBuilder(); -    var forwardCriteria = Bookmark.queryViewable(entityClass, viewer, owner, cursor, cb, true); +    var forwardCriteria = queryViewable(entityClass, viewer, owner, cursor, cb, true);      var forwardQuery = session.createQuery(forwardCriteria);      if (count != null) { @@ -186,7 +203,7 @@ public abstract class Post extends PanacheEntityBase {      if (cursor != null) {        // Look backwards as well so we can find the prevCursor. -      var backwardCriteria = Bookmark.queryViewable(entityClass, viewer, owner, cursor, cb, false); +      var backwardCriteria = queryViewable(entityClass, viewer, owner, cursor, cb, false);        var backwardQuery = session.createQuery(backwardCriteria);        backwardQuery.setMaxResults(count);        var backwardResults = backwardQuery.getResultList(); @@ -195,7 +212,7 @@ public abstract class Post extends PanacheEntityBase {        }      } -    var forwardResults = forwardQuery.getResultList(); +    var forwardResults = (List<T>) forwardQuery.getResultList();      if (count != null) {        if (forwardResults.size() == count + 1) {          nextCursor = forwardResults.get(count).id; @@ -203,6 +220,6 @@ public abstract class Post extends PanacheEntityBase {        }      } -    return new PostPage(prevCursor, cursor, nextCursor, forwardResults); +    return new PostPage<T>(prevCursor, cursor, nextCursor, forwardResults);    }  } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostFilter.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostFilter.java new file mode 100644 index 0000000..94069e3 --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostFilter.java @@ -0,0 +1,7 @@ +package eu.mulk.mulkcms2.benki.posts; + +public enum PostFilter { +  BOOKMARKS_ONLY, +  LAZYCHAT_MESSAGES_ONLY, +  ALL, +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java new file mode 100644 index 0000000..e08aaf1 --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java @@ -0,0 +1,253 @@ +package eu.mulk.mulkcms2.benki.posts; + +import static javax.ws.rs.core.MediaType.APPLICATION_ATOM_XML; +import static javax.ws.rs.core.MediaType.TEXT_HTML; + +import com.rometools.rome.feed.atom.Content; +import com.rometools.rome.feed.atom.Entry; +import com.rometools.rome.feed.atom.Feed; +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.bookmarks.Bookmark; +import eu.mulk.mulkcms2.benki.users.User; +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.net.URI; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.json.spi.JsonProvider; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.ws.rs.GET; +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.UriInfo; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.hibernate.Session; +import org.jboss.logging.Logger; + +public abstract class PostResource { + +  private static final Logger log = Logger.getLogger(PostResource.class); + +  private static final DateTimeFormatter htmlDateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + +  private static final DateTimeFormatter humanDateFormatter = +      DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); + +  protected static final JsonProvider jsonProvider = JsonProvider.provider(); + +  @ConfigProperty(name = "mulkcms.posts.default-max-results") +  int defaultMaxResults; + +  @ResourcePath("benki/posts/postList.html") +  @Inject +  Template postList; + +  @Inject protected SecurityIdentity identity; + +  @Context protected UriInfo uri; + +  @Inject +  @ConfigProperty(name = "mulkcms.tag-base") +  String tagBase; + +  @PersistenceContext EntityManager entityManager; + +  private final PostFilter postFilter; +  private final String pageTitle; + +  public PostResource(PostFilter postFilter, String pageTitle) { +    this.postFilter = postFilter; +    this.pageTitle = pageTitle; +  } + +  @GET +  @Produces(TEXT_HTML) +  public TemplateInstance getIndex( +      @QueryParam("i") @CheckForNull Integer cursor, +      @QueryParam("n") @CheckForNull Integer maxResults) { + +    maxResults = maxResults == null ? defaultMaxResults : maxResults; + +    var session = entityManager.unwrap(Session.class); +    var q = Post.findViewable(postFilter, session, identity, null, cursor, maxResults); + +    return postList +        .data("posts", q.posts) +        .data("feedUri", "/bookmarks/feed") +        .data("pageTitle", pageTitle) +        .data("showBookmarkForm", showBookmarkForm()) +        .data("showLazychatForm", showLazychatForm()) +        .data("hasPreviousPage", q.prevCursor != null) +        .data("hasNextPage", q.nextCursor != null) +        .data("previousCursor", q.prevCursor) +        .data("nextCursor", q.nextCursor) +        .data("pageSize", maxResults); +  } + +  @GET +  @Path("~{ownerName}") +  @Produces(TEXT_HTML) +  public TemplateInstance getUserIndex( +      @PathParam("ownerName") String ownerName, +      @QueryParam("i") @CheckForNull Integer cursor, +      @QueryParam("n") @CheckForNull Integer maxResults) { + +    maxResults = maxResults == null ? defaultMaxResults : maxResults; + +    var owner = User.findByNickname(ownerName); +    var session = entityManager.unwrap(Session.class); +    var q = Post.findViewable(postFilter, session, identity, owner, cursor, maxResults); + +    return postList +        .data("posts", q.posts) +        .data("feedUri", String.format("/bookmarks/~%s/feed", ownerName)) +        .data("pageTitle", pageTitle) +        .data("showBookmarkForm", showBookmarkForm()) +        .data("showLazychatForm", showLazychatForm()) +        .data("hasPreviousPage", q.prevCursor != null) +        .data("hasNextPage", q.nextCursor != null) +        .data("previousCursor", q.prevCursor) +        .data("nextCursor", q.nextCursor) +        .data("pageSize", maxResults); +  } + +  @GET +  @Path("feed") +  @Produces(APPLICATION_ATOM_XML) +  public String getFeed() throws FeedException { +    return makeFeed(null, null); +  } + +  @GET +  @Path("~{ownerName}/feed") +  @Produces(APPLICATION_ATOM_XML) +  public String getUserFeed(@PathParam("ownerName") String ownerName) throws FeedException { +    var owner = User.findByNickname(ownerName); +    return makeFeed(owner, ownerName); +  } + +  private String makeFeed(@Nullable User owner, @Nullable String ownerName) throws FeedException { +    var bookmarks = Bookmark.findViewable(entityManager.unwrap(Session.class), identity, owner); +    var feed = new Feed("atom_1.0"); + +    var feedSubId = owner == null ? "" : String.format("/%d", owner.id); + +    feed.setTitle("Book Marx"); +    feed.setId( +        String.format( +            "tag:%s,2019:marx%s:%s", +            tagBase, +            feedSubId, +            identity.isAnonymous() ? "world" : identity.getPrincipal().getName())); +    feed.setUpdated( +        Date.from( +            bookmarks.stream() +                .map(x -> x.date) +                .max(Comparator.comparing(x -> x)) +                .orElse(OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)) +                .toInstant())); + +    var selfLink = new Link(); +    selfLink.setHref(uri.getRequestUri().toString()); +    selfLink.setRel("self"); +    feed.setOtherLinks(List.of(selfLink)); + +    var htmlAltLink = new Link(); +    var htmlAltPath = owner == null ? "/bookmarks" : String.format("~%s/bookmarks", ownerName); +    htmlAltLink.setHref(uri.resolve(URI.create(htmlAltPath)).toString()); +    htmlAltLink.setRel("alternate"); +    htmlAltLink.setType("text/html"); +    feed.setAlternateLinks(List.of(htmlAltLink)); + +    feed.setEntries( +        bookmarks.stream() +            .map( +                bookmark -> { +                  var entry = new Entry(); + +                  entry.setId(String.format("tag:%s,2012:/marx/%d", tagBase, bookmark.id)); +                  entry.setPublished(Date.from(bookmark.date.toInstant())); +                  entry.setUpdated(Date.from(bookmark.date.toInstant())); + +                  var author = new SyndPersonImpl(); +                  author.setName(bookmark.owner.getFirstAndLastName()); +                  entry.setAuthors(List.of(author)); + +                  var title = new Content(); +                  title.setType("text"); +                  title.setValue(bookmark.title); +                  entry.setTitleEx(title); + +                  var summary = new Content(); +                  summary.setType("html"); +                  summary.setValue(bookmark.getDescriptionHtml()); +                  entry.setSummary(summary); + +                  var link = new Link(); +                  link.setHref(bookmark.uri); +                  link.setRel("alternate"); +                  entry.setAlternateLinks(List.of(link)); + +                  return entry; +                }) +            .collect(Collectors.toUnmodifiableList())); + +    var wireFeedOutput = new WireFeedOutput(); +    return wireFeedOutput.outputString(feed); +  } + +  @TemplateExtension +  static String humanDateTime(TemporalAccessor x) { +    return humanDateFormatter.format(x); +  } + +  @TemplateExtension +  static String htmlDateTime(TemporalAccessor x) { +    return htmlDateFormatter.format(x); +  } + +  private boolean showBookmarkForm() { +    switch (postFilter) { +      case ALL: +      case BOOKMARKS_ONLY: +        return !identity.isAnonymous(); +      case LAZYCHAT_MESSAGES_ONLY: +        return false; +      default: +        throw new IllegalStateException(); +    } +  } + +  private boolean showLazychatForm() { +    switch (postFilter) { +      case ALL: +      case LAZYCHAT_MESSAGES_ONLY: +        return !identity.isAnonymous(); +      case BOOKMARKS_ONLY: +        return false; +      default: +        throw new IllegalStateException(); +    } +  } +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/generic/PostTarget.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTarget.java index 7073874..112ca3e 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/generic/PostTarget.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTarget.java @@ -1,4 +1,4 @@ -package eu.mulk.mulkcms2.benki.generic; +package eu.mulk.mulkcms2.benki.posts;  import eu.mulk.mulkcms2.benki.accesscontrol.Role;  import io.quarkus.hibernate.orm.panache.PanacheEntityBase; diff --git a/src/main/java/eu/mulk/mulkcms2/benki/generic/PostTargetPK.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTargetPK.java index 13c660d..ecd5861 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/generic/PostTargetPK.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTargetPK.java @@ -1,4 +1,4 @@ -package eu.mulk.mulkcms2.benki.generic; +package eu.mulk.mulkcms2.benki.posts;  import java.io.Serializable;  import javax.persistence.Column; 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 6587ec4..5879046 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java @@ -3,8 +3,8 @@ package eu.mulk.mulkcms2.benki.users;  import eu.mulk.mulkcms2.benki.accesscontrol.PageKey;  import eu.mulk.mulkcms2.benki.accesscontrol.Role;  import eu.mulk.mulkcms2.benki.bookmarks.Bookmark; -import eu.mulk.mulkcms2.benki.generic.Post;  import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage; +import eu.mulk.mulkcms2.benki.posts.Post;  import eu.mulk.mulkcms2.benki.wiki.WikiPageRevision;  import io.quarkus.hibernate.orm.panache.PanacheEntityBase;  import java.util.Collection; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bf3b1a4..80a4620 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,8 +8,7 @@ quarkus.log.level = INFO  #quarkus.log.category."io.vertx.ext.jwt".level = FINEST  mulkcms.tag-base = hub.benkard.de -mulkcms.bookmarks.default-max-results = 25 -mulkcms.lazychat.default-max-results = 25 +mulkcms.posts.default-max-results = 25  quarkus.datasource.driver = org.postgresql.Driver  quarkus.datasource.max-size = 8  | 
