| 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(); |
| } |
| } |
| } |