diff options
Diffstat (limited to 'src/main')
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 |