blob: 16223345e406c397e7c7b654a9cb4b87d5e0d4a1 [file] [log] [blame]
package eu.mulk.mulkcms2.benki.wiki;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.TEXT_HTML;
import eu.mulk.mulkcms2.benki.users.User;
import io.quarkus.panache.common.Sort;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.qute.TemplateInstance;
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.Optional;
import javax.annotation.CheckForNull;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.json.spi.JsonProvider;
import javax.transaction.Transactional;
import javax.ws.rs.BadRequestException;
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;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
@Path("/wiki")
public class WikiResource {
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();
@CheckedTemplate(basePath = "benki/wiki")
static class Templates {
public static native TemplateInstance wikiPage(WikiPageRevision page);
public static native TemplateInstance wikiPageRevisionList(WikiPage page, String title);
}
@Inject SecurityIdentity identity;
@GET
public Response getRoot() throws URISyntaxException {
return Response.seeOther(new URI("/wiki/Home")).build();
}
@GET
@Path("{pageName}")
@Produces(TEXT_HTML)
@Authenticated
public TemplateInstance getPage(@PathParam("pageName") String pageName) {
Optional<WikiPageRevision> maybePage =
WikiPageRevision.find(
"from WikiPageRevision rev join fetch rev.author where rev.title = ?1",
Sort.by("date").descending(),
pageName)
.firstResultOptional();
if (maybePage.isEmpty()) {
throw new NotFoundException();
}
var page = maybePage.get();
return Templates.wikiPage(page);
}
@POST
@Path("{pageName}")
@Authenticated
@Transactional
@Produces(APPLICATION_JSON)
public JsonObject updatePage(
@PathParam("pageName") String pageName,
@FormParam("wiki-title") @CheckForNull String title,
@FormParam("wiki-content") @CheckForNull String content) {
if (title == null && content == null) {
// No changes, nothing to do.
return jsonProvider.createObjectBuilder().add("status", "ok").build();
}
if (title != null) {
// Remove markup. Reject whitespace.
title = Jsoup.clean(title, Whitelist.none());
if (!title.matches("\\w+")) {
throw new BadRequestException("title does not match \"\\w+\"");
}
}
var userName = identity.getPrincipal().getName();
Optional<WikiPageRevision> maybeCurrentRevision =
WikiPageRevision.find(
"from WikiPageRevision rev join fetch rev.author where rev.title = ?1",
Sort.by("date").descending(),
pageName)
.firstResultOptional();
if (maybeCurrentRevision.isEmpty()) {
throw new NotFoundException();
}
var currentRevision = maybeCurrentRevision.get();
var pageRevision =
new WikiPageRevision(
OffsetDateTime.now(),
title != null ? title : currentRevision.title,
content != null ? content : currentRevision.content,
"html5",
currentRevision.page,
User.find("from BenkiUser u join u.nicknames n where ?1 = n", userName).singleResult());
pageRevision.persistAndFlush();
return jsonProvider
.createObjectBuilder()
.add("status", "ok")
.add("content", pageRevision.enrichedContent())
.build();
}
@GET
@Path("{pageName}/revisions")
@Produces(TEXT_HTML)
@Authenticated
public TemplateInstance getPageRevisions(@PathParam("pageName") String pageName) {
Optional<WikiPageRevision> maybePrimaryRevision =
WikiPageRevision.find(
"from WikiPageRevision rev join fetch rev.author where rev.title = ?1",
Sort.by("date").descending(),
pageName)
.firstResultOptional();
if (maybePrimaryRevision.isEmpty()) {
throw new NotFoundException();
}
var primaryRevision = maybePrimaryRevision.get();
WikiPage page =
WikiPageRevision.find(
"from WikiPage p"
+ " join fetch p.revisions rev"
+ " join fetch rev.author"
+ " where p.id = ?1",
primaryRevision.page.id)
.singleResult();
return Templates.wikiPageRevisionList(page, pageName);
}
@TemplateExtension
@CheckForNull
static String humanDateTime(@CheckForNull TemporalAccessor x) {
if (x == null) {
return null;
}
return humanDateFormatter.format(x);
}
@TemplateExtension
@CheckForNull
static String htmlDateTime(@CheckForNull TemporalAccessor x) {
if (x == null) {
return null;
}
return htmlDateFormatter.format(x);
}
}