blob: 6351aab95c05535e6d5f821bafc67bac38a735b5 [file] [log] [blame]
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +01001package eu.mulk.mulkcms2.benki.wiki;
2
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +01003import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +01004import static javax.ws.rs.core.MediaType.TEXT_HTML;
5
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +01006import eu.mulk.mulkcms2.benki.users.User;
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +01007import io.quarkus.panache.common.Sort;
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +02008import io.quarkus.qute.CheckedTemplate;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +01009import io.quarkus.qute.TemplateInstance;
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010010import io.quarkus.security.Authenticated;
11import io.quarkus.security.identity.SecurityIdentity;
Matthias Andreas Benkard521718d2020-02-09 06:47:05 +010012import java.net.URI;
13import java.net.URISyntaxException;
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +010014import java.time.OffsetDateTime;
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +010015import java.time.ZoneOffset;
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010016import java.util.Optional;
Matthias Andreas Benkard1e7674c2020-04-18 20:28:51 +020017import javax.annotation.CheckForNull;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010018import javax.inject.Inject;
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +010019import javax.json.JsonObject;
20import javax.json.spi.JsonProvider;
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +010021import javax.transaction.Transactional;
22import javax.ws.rs.BadRequestException;
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +010023import javax.ws.rs.FormParam;
Matthias Andreas Benkardbfdbd192020-01-24 14:45:10 +010024import javax.ws.rs.GET;
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010025import javax.ws.rs.NotFoundException;
26import javax.ws.rs.POST;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010027import javax.ws.rs.Path;
28import javax.ws.rs.PathParam;
29import javax.ws.rs.Produces;
Matthias Andreas Benkard521718d2020-02-09 06:47:05 +010030import javax.ws.rs.core.Response;
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +010031import org.jsoup.Jsoup;
Matthias Andreas Benkard0dfcd142022-03-19 13:19:20 +010032import org.jsoup.safety.Safelist;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010033
34@Path("/wiki")
35public class WikiResource {
36
Matthias Andreas Benkard593765d2020-04-18 20:44:07 +020037 private static final JsonProvider jsonProvider = JsonProvider.provider();
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +010038
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +020039 @CheckedTemplate(basePath = "benki/wiki")
40 static class Templates {
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010041
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +020042 public static native TemplateInstance wikiPage(WikiPageRevision page);
43
44 public static native TemplateInstance wikiPageRevisionList(WikiPage page, String title);
45 }
Matthias Andreas Benkard0aac7c52020-01-26 08:05:27 +010046
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010047 @Inject SecurityIdentity identity;
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010048
49 @GET
Matthias Andreas Benkard521718d2020-02-09 06:47:05 +010050 public Response getRoot() throws URISyntaxException {
51 return Response.seeOther(new URI("/wiki/Home")).build();
52 }
53
54 @GET
Matthias Andreas Benkard4d8139f2020-09-10 15:39:10 +020055 @Path("{pageName}")
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010056 @Produces(TEXT_HTML)
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +010057 @Authenticated
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +010058 public TemplateInstance getPage(@PathParam("pageName") String pageName) {
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +010059 WikiPageRevision page;
60
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010061 Optional<WikiPageRevision> maybePage =
Matthias Andreas Benkard514273e2020-01-24 19:18:01 +010062 WikiPageRevision.find(
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +010063 "from WikiPageRevision rev join fetch rev.author where rev.title = ?1",
Matthias Andreas Benkard514273e2020-01-24 19:18:01 +010064 Sort.by("date").descending(),
65 pageName)
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010066 .firstResultOptional();
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +010067 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 Benkard9222efa2020-01-24 19:11:24 +010079 }
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +010080
Matthias Andreas Benkard49b01512021-07-05 06:45:54 +020081 return Templates.wikiPage(page);
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010082 }
83
84 @POST
Matthias Andreas Benkard4d8139f2020-09-10 15:39:10 +020085 @Path("{pageName}")
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +010086 @Authenticated
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +010087 @Transactional
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +010088 @Produces(APPLICATION_JSON)
89 public JsonObject updatePage(
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +010090 @PathParam("pageName") String pageName,
Matthias Andreas Benkard1e7674c2020-04-18 20:28:51 +020091 @FormParam("wiki-title") @CheckForNull String title,
92 @FormParam("wiki-content") @CheckForNull String content) {
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +010093
94 if (title == null && content == null) {
95 // No changes, nothing to do.
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +010096 return jsonProvider.createObjectBuilder().add("status", "ok").build();
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +010097 }
98
99 if (title != null) {
100 // Remove markup. Reject whitespace.
Matthias Andreas Benkard0dfcd142022-03-19 13:19:20 +0100101 title = Jsoup.clean(title, Safelist.none());
Matthias Andreas Benkardd50bbb72020-01-27 05:53:07 +0100102 if (!title.matches("\\w+")) {
103 throw new BadRequestException("title does not match \"\\w+\"");
104 }
105 }
106
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +0100107 var userName = identity.getPrincipal().getName();
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +0100108 User user =
109 User.find("from BenkiUser u join u.nicknames n where ?1 = n", userName).singleResult();
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +0100110
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 Benkarda84be3b2022-11-02 05:33:37 +0100117
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 Benkard9222efa2020-01-24 19:11:24 +0100133 }
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +0100134
135 var pageRevision =
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +0100136 new WikiPageRevision(OffsetDateTime.now(), title, content, "html5", page, user);
137 pageRevision.persist();
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +0100138
139 return jsonProvider
140 .createObjectBuilder()
141 .add("status", "ok")
142 .add("content", pageRevision.enrichedContent())
Matthias Andreas Benkarda84be3b2022-11-02 05:33:37 +0100143 .add("title", pageRevision.title)
Matthias Andreas Benkard2f537452020-01-27 21:23:00 +0100144 .build();
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +0100145 }
146
147 @GET
Matthias Andreas Benkard4d8139f2020-09-10 15:39:10 +0200148 @Path("{pageName}/revisions")
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +0100149 @Produces(TEXT_HTML)
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +0100150 @Authenticated
Matthias Andreas Benkard9222efa2020-01-24 19:11:24 +0100151 public TemplateInstance getPageRevisions(@PathParam("pageName") String pageName) {
Matthias Andreas Benkard0aac7c52020-01-26 08:05:27 +0100152 Optional<WikiPageRevision> maybePrimaryRevision =
153 WikiPageRevision.find(
Matthias Andreas Benkard5d075272020-01-26 18:05:10 +0100154 "from WikiPageRevision rev join fetch rev.author where rev.title = ?1",
Matthias Andreas Benkard0aac7c52020-01-26 08:05:27 +0100155 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 Benkard49b01512021-07-05 06:45:54 +0200172 return Templates.wikiPageRevisionList(page, pageName);
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +0100173 }
Matthias Andreas Benkard26de0cc2020-01-24 14:44:20 +0100174}