Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 1 | package eu.mulk.mulkcms2.benki.wiki; |
| 2 | |
Matthias Andreas Benkard | e3bc3ee | 2023-08-06 16:21:11 +0200 | [diff] [blame] | 3 | import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; |
| 4 | import static jakarta.ws.rs.core.MediaType.TEXT_HTML; |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 5 | |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 6 | import eu.mulk.mulkcms2.benki.users.User; |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 7 | import io.quarkus.panache.common.Sort; |
Matthias Andreas Benkard | 49b0151 | 2021-07-05 06:45:54 +0200 | [diff] [blame] | 8 | import io.quarkus.qute.CheckedTemplate; |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 9 | import io.quarkus.qute.TemplateInstance; |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 10 | import io.quarkus.security.Authenticated; |
| 11 | import io.quarkus.security.identity.SecurityIdentity; |
Matthias Andreas Benkard | e3bc3ee | 2023-08-06 16:21:11 +0200 | [diff] [blame] | 12 | import jakarta.inject.Inject; |
| 13 | import jakarta.json.JsonObject; |
| 14 | import jakarta.json.spi.JsonProvider; |
| 15 | import jakarta.transaction.Transactional; |
| 16 | import jakarta.ws.rs.BadRequestException; |
| 17 | import jakarta.ws.rs.FormParam; |
| 18 | import jakarta.ws.rs.GET; |
| 19 | import jakarta.ws.rs.NotFoundException; |
| 20 | import jakarta.ws.rs.POST; |
| 21 | import jakarta.ws.rs.Path; |
| 22 | import jakarta.ws.rs.PathParam; |
| 23 | import jakarta.ws.rs.Produces; |
| 24 | import jakarta.ws.rs.core.Response; |
Matthias Andreas Benkard | 521718d | 2020-02-09 06:47:05 +0100 | [diff] [blame] | 25 | import java.net.URI; |
| 26 | import java.net.URISyntaxException; |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 27 | import java.time.OffsetDateTime; |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 28 | import java.time.ZoneOffset; |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 29 | import java.util.Optional; |
Matthias Andreas Benkard | 1e7674c | 2020-04-18 20:28:51 +0200 | [diff] [blame] | 30 | import javax.annotation.CheckForNull; |
Matthias Andreas Benkard | d50bbb7 | 2020-01-27 05:53:07 +0100 | [diff] [blame] | 31 | import org.jsoup.Jsoup; |
Matthias Andreas Benkard | 0dfcd14 | 2022-03-19 13:19:20 +0100 | [diff] [blame] | 32 | import org.jsoup.safety.Safelist; |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 33 | |
| 34 | @Path("/wiki") |
| 35 | public class WikiResource { |
| 36 | |
Matthias Andreas Benkard | 593765d | 2020-04-18 20:44:07 +0200 | [diff] [blame] | 37 | private static final JsonProvider jsonProvider = JsonProvider.provider(); |
Matthias Andreas Benkard | 2f53745 | 2020-01-27 21:23:00 +0100 | [diff] [blame] | 38 | |
Matthias Andreas Benkard | 49b0151 | 2021-07-05 06:45:54 +0200 | [diff] [blame] | 39 | @CheckedTemplate(basePath = "benki/wiki") |
| 40 | static class Templates { |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 41 | |
Matthias Andreas Benkard | 49b0151 | 2021-07-05 06:45:54 +0200 | [diff] [blame] | 42 | public static native TemplateInstance wikiPage(WikiPageRevision page); |
| 43 | |
| 44 | public static native TemplateInstance wikiPageRevisionList(WikiPage page, String title); |
| 45 | } |
Matthias Andreas Benkard | 0aac7c5 | 2020-01-26 08:05:27 +0100 | [diff] [blame] | 46 | |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 47 | @Inject SecurityIdentity identity; |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 48 | |
| 49 | @GET |
Matthias Andreas Benkard | 521718d | 2020-02-09 06:47:05 +0100 | [diff] [blame] | 50 | public Response getRoot() throws URISyntaxException { |
| 51 | return Response.seeOther(new URI("/wiki/Home")).build(); |
| 52 | } |
| 53 | |
| 54 | @GET |
Matthias Andreas Benkard | 4d8139f | 2020-09-10 15:39:10 +0200 | [diff] [blame] | 55 | @Path("{pageName}") |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 56 | @Produces(TEXT_HTML) |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 57 | @Authenticated |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 58 | public TemplateInstance getPage(@PathParam("pageName") String pageName) { |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 59 | WikiPageRevision page; |
| 60 | |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 61 | Optional<WikiPageRevision> maybePage = |
Matthias Andreas Benkard | 514273e | 2020-01-24 19:18:01 +0100 | [diff] [blame] | 62 | WikiPageRevision.find( |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 63 | "from WikiPageRevision rev join fetch rev.author where rev.title = ?1", |
Matthias Andreas Benkard | 514273e | 2020-01-24 19:18:01 +0100 | [diff] [blame] | 64 | Sort.by("date").descending(), |
| 65 | pageName) |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 66 | .firstResultOptional(); |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 67 | if (maybePage.isPresent()) { |
| 68 | page = maybePage.get(); |
| 69 | } else { |
| 70 | var userName = identity.getPrincipal().getName(); |
| 71 | User user = |
| 72 | User.find("from BenkiUser u join u.nicknames n where ?1 = n", userName).singleResult(); |
| 73 | page = new WikiPageRevision(); |
| 74 | page.content = ""; |
| 75 | page.title = pageName; |
| 76 | page.date = OffsetDateTime.now(ZoneOffset.UTC); |
| 77 | page.format = "html5"; |
| 78 | page.author = user; |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 79 | } |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 80 | |
Matthias Andreas Benkard | 49b0151 | 2021-07-05 06:45:54 +0200 | [diff] [blame] | 81 | return Templates.wikiPage(page); |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | @POST |
Matthias Andreas Benkard | 4d8139f | 2020-09-10 15:39:10 +0200 | [diff] [blame] | 85 | @Path("{pageName}") |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 86 | @Authenticated |
Matthias Andreas Benkard | d50bbb7 | 2020-01-27 05:53:07 +0100 | [diff] [blame] | 87 | @Transactional |
Matthias Andreas Benkard | 2f53745 | 2020-01-27 21:23:00 +0100 | [diff] [blame] | 88 | @Produces(APPLICATION_JSON) |
| 89 | public JsonObject updatePage( |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 90 | @PathParam("pageName") String pageName, |
Matthias Andreas Benkard | 1e7674c | 2020-04-18 20:28:51 +0200 | [diff] [blame] | 91 | @FormParam("wiki-title") @CheckForNull String title, |
| 92 | @FormParam("wiki-content") @CheckForNull String content) { |
Matthias Andreas Benkard | d50bbb7 | 2020-01-27 05:53:07 +0100 | [diff] [blame] | 93 | |
| 94 | if (title == null && content == null) { |
| 95 | // No changes, nothing to do. |
Matthias Andreas Benkard | 2f53745 | 2020-01-27 21:23:00 +0100 | [diff] [blame] | 96 | return jsonProvider.createObjectBuilder().add("status", "ok").build(); |
Matthias Andreas Benkard | d50bbb7 | 2020-01-27 05:53:07 +0100 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | if (title != null) { |
| 100 | // Remove markup. Reject whitespace. |
Matthias Andreas Benkard | 0dfcd14 | 2022-03-19 13:19:20 +0100 | [diff] [blame] | 101 | title = Jsoup.clean(title, Safelist.none()); |
Matthias Andreas Benkard | d50bbb7 | 2020-01-27 05:53:07 +0100 | [diff] [blame] | 102 | if (!title.matches("\\w+")) { |
| 103 | throw new BadRequestException("title does not match \"\\w+\""); |
| 104 | } |
| 105 | } |
| 106 | |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 107 | var userName = identity.getPrincipal().getName(); |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 108 | User user = |
| 109 | User.find("from BenkiUser u join u.nicknames n where ?1 = n", userName).singleResult(); |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 110 | |
| 111 | Optional<WikiPageRevision> maybeCurrentRevision = |
| 112 | WikiPageRevision.find( |
| 113 | "from WikiPageRevision rev join fetch rev.author where rev.title = ?1", |
| 114 | Sort.by("date").descending(), |
| 115 | pageName) |
| 116 | .firstResultOptional(); |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 117 | |
| 118 | final WikiPage page; |
| 119 | if (maybeCurrentRevision.isPresent()) { |
| 120 | // Update the existing page. |
| 121 | var currentRevision = maybeCurrentRevision.get(); |
| 122 | page = currentRevision.page; |
| 123 | |
| 124 | title = title != null ? title : currentRevision.title; |
| 125 | content = content != null ? content : currentRevision.content; |
| 126 | } else { |
| 127 | // Create a new page. |
| 128 | page = new WikiPage(); |
| 129 | page.persist(); |
| 130 | |
| 131 | title = title != null ? title : pageName; |
| 132 | content = content != null ? content : ""; |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 133 | } |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 134 | |
| 135 | var pageRevision = |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 136 | new WikiPageRevision(OffsetDateTime.now(), title, content, "html5", page, user); |
| 137 | pageRevision.persist(); |
Matthias Andreas Benkard | 2f53745 | 2020-01-27 21:23:00 +0100 | [diff] [blame] | 138 | |
| 139 | return jsonProvider |
| 140 | .createObjectBuilder() |
| 141 | .add("status", "ok") |
| 142 | .add("content", pageRevision.enrichedContent()) |
Matthias Andreas Benkard | a84be3b | 2022-11-02 05:33:37 +0100 | [diff] [blame] | 143 | .add("title", pageRevision.title) |
Matthias Andreas Benkard | 2f53745 | 2020-01-27 21:23:00 +0100 | [diff] [blame] | 144 | .build(); |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 145 | } |
| 146 | |
| 147 | @GET |
Matthias Andreas Benkard | 4d8139f | 2020-09-10 15:39:10 +0200 | [diff] [blame] | 148 | @Path("{pageName}/revisions") |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 149 | @Produces(TEXT_HTML) |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 150 | @Authenticated |
Matthias Andreas Benkard | 9222efa | 2020-01-24 19:11:24 +0100 | [diff] [blame] | 151 | public TemplateInstance getPageRevisions(@PathParam("pageName") String pageName) { |
Matthias Andreas Benkard | 0aac7c5 | 2020-01-26 08:05:27 +0100 | [diff] [blame] | 152 | Optional<WikiPageRevision> maybePrimaryRevision = |
| 153 | WikiPageRevision.find( |
Matthias Andreas Benkard | 5d07527 | 2020-01-26 18:05:10 +0100 | [diff] [blame] | 154 | "from WikiPageRevision rev join fetch rev.author where rev.title = ?1", |
Matthias Andreas Benkard | 0aac7c5 | 2020-01-26 08:05:27 +0100 | [diff] [blame] | 155 | Sort.by("date").descending(), |
| 156 | pageName) |
| 157 | .firstResultOptional(); |
| 158 | if (maybePrimaryRevision.isEmpty()) { |
| 159 | throw new NotFoundException(); |
| 160 | } |
| 161 | var primaryRevision = maybePrimaryRevision.get(); |
| 162 | |
| 163 | WikiPage page = |
| 164 | WikiPageRevision.find( |
| 165 | "from WikiPage p" |
| 166 | + " join fetch p.revisions rev" |
| 167 | + " join fetch rev.author" |
| 168 | + " where p.id = ?1", |
| 169 | primaryRevision.page.id) |
| 170 | .singleResult(); |
| 171 | |
Matthias Andreas Benkard | 49b0151 | 2021-07-05 06:45:54 +0200 | [diff] [blame] | 172 | return Templates.wikiPageRevisionList(page, pageName); |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 173 | } |
Matthias Andreas Benkard | 26de0cc | 2020-01-24 14:44:20 +0100 | [diff] [blame] | 174 | } |