make it work and make it better
added html, redirects, did you mean, maybe other stuff I forgot
This commit is contained in:
parent
b8d1a0512c
commit
f0049a4d93
18 changed files with 318 additions and 119 deletions
7
pom.xml
7
pom.xml
|
@ -30,6 +30,13 @@
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.18.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-rest</artifactId>
|
<artifactId>quarkus-rest</artifactId>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
import org.jboss.resteasy.reactive.RestResponse;
|
import org.jboss.resteasy.reactive.RestResponse;
|
||||||
import org.jboss.resteasy.reactive.RestResponse.Status;
|
import org.jboss.resteasy.reactive.RestResponse.Status;
|
||||||
|
|
||||||
|
|
42
src/main/java/eu/m724/talkpages/Startup.java
Normal file
42
src/main/java/eu/m724/talkpages/Startup.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package eu.m724.talkpages;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.orm.entity.Account;
|
||||||
|
import eu.m724.talkpages.orm.entity.Page;
|
||||||
|
import eu.m724.talkpages.orm.entity.PageRevision;
|
||||||
|
import io.quarkus.runtime.LaunchMode;
|
||||||
|
import io.quarkus.runtime.StartupEvent;
|
||||||
|
import jakarta.enterprise.event.Observes;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class Startup {
|
||||||
|
@Inject
|
||||||
|
LaunchMode launchMode;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void examplePage(@Observes StartupEvent ignoredEvent) {
|
||||||
|
Account account = new Account("System");
|
||||||
|
account.persistAndFlush();
|
||||||
|
|
||||||
|
addPage(account, "TalkPages", "<p>A website where the users collaboratively create content</p><ul><li><a href=\"https://git.m724.eu/Minecon724/talkpages\">Source code</a></li></ul>");
|
||||||
|
addPage(account, "Talkpages", "ambiguous for [TalkPages]");
|
||||||
|
addPage(account, "TP", "@TalkPages");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void addPage(Account account, String title, String content) {
|
||||||
|
Page page = new Page(title);
|
||||||
|
|
||||||
|
PageRevision revision = new PageRevision(page);
|
||||||
|
revision.author = account;
|
||||||
|
revision.content = content;
|
||||||
|
revision.delta = revision.content.length();
|
||||||
|
|
||||||
|
page.setLatestRevision(revision);
|
||||||
|
account.revisions.add(revision);
|
||||||
|
|
||||||
|
revision.persistAndFlush();
|
||||||
|
}
|
||||||
|
}
|
24
src/main/java/eu/m724/talkpages/TemplateExtensions.java
Normal file
24
src/main/java/eu/m724/talkpages/TemplateExtensions.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package eu.m724.talkpages;
|
||||||
|
|
||||||
|
import io.quarkus.qute.TemplateExtension;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.safety.Safelist;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class TemplateExtensions {
|
||||||
|
// disallowing images because abuse risk and a text only site is refreshing
|
||||||
|
private static final Safelist safelist = Safelist.relaxed().removeTags("img");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize HTML. This means remove <script> and stuff
|
||||||
|
*
|
||||||
|
* @param content the HTML content
|
||||||
|
* @return sanitized HTML content
|
||||||
|
*/
|
||||||
|
@TemplateExtension
|
||||||
|
public static String sanitizeContent(String content) {
|
||||||
|
// I was thinking maybe we could put somewhere that it was sanitized
|
||||||
|
return Jsoup.clean(content, safelist);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package eu.m724.talkpages.orm.entity;
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -10,6 +11,12 @@ import java.util.List;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public class Page extends PanacheEntityBase {
|
public class Page extends PanacheEntityBase {
|
||||||
|
public Page() {}
|
||||||
|
|
||||||
|
public Page(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(generator = "title-path")
|
@GeneratedValue(generator = "title-path")
|
||||||
@GenericGenerator(name = "title-path", strategy = "eu.m724.talkpages.orm.generator.PathGenerator")
|
@GenericGenerator(name = "title-path", strategy = "eu.m724.talkpages.orm.generator.PathGenerator")
|
||||||
|
@ -33,4 +40,13 @@ public class Page extends PanacheEntityBase {
|
||||||
public static Page findByTitle(String title) {
|
public static Page findByTitle(String title) {
|
||||||
return find("title", title).firstResult();
|
return find("title", title).firstResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Page> findByTitleIgnoreCase(String title) {
|
||||||
|
return find("lower(title) = ?1", title.toLowerCase()).list();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLatestRevision(PageRevision pageRevision) {
|
||||||
|
revisions.add(pageRevision);
|
||||||
|
latestRevision = pageRevision;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,19 @@ import java.time.LocalDateTime;
|
||||||
uniqueConstraints = @UniqueConstraint(columnNames = {"id", "index"})
|
uniqueConstraints = @UniqueConstraint(columnNames = {"id", "index"})
|
||||||
)
|
)
|
||||||
public class PageRevision extends PanacheEntity {
|
public class PageRevision extends PanacheEntity {
|
||||||
@ManyToOne
|
public PageRevision() {}
|
||||||
|
|
||||||
|
public PageRevision(Page page) {
|
||||||
|
long index = 1;
|
||||||
|
if (page.latestRevision != null) {
|
||||||
|
index = page.latestRevision.index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.index = index;
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(cascade = CascadeType.ALL)
|
||||||
public Page page;
|
public Page page;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +41,7 @@ public class PageRevision extends PanacheEntity {
|
||||||
/**
|
/**
|
||||||
* The author of the revision
|
* The author of the revision
|
||||||
*/
|
*/
|
||||||
@ManyToOne
|
@ManyToOne(cascade = CascadeType.ALL)
|
||||||
public Account author;
|
public Account author;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
package eu.m724.talkpages.page;
|
|
||||||
|
|
||||||
import eu.m724.talkpages.RedirectService;
|
|
||||||
import eu.m724.talkpages.orm.entity.Account;
|
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
import jakarta.ws.rs.Consumes;
|
|
||||||
import jakarta.ws.rs.POST;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
import org.jboss.resteasy.reactive.RestResponse;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
@Path("/action")
|
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
|
||||||
public class ActionResource {
|
|
||||||
@Inject
|
|
||||||
RedirectService redirectService;
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/create")
|
|
||||||
@Transactional // TODO make this in a service or whatever is it called
|
|
||||||
public Response create(MultivaluedMap<String, String> formData) {
|
|
||||||
String title = formData.getFirst("title");
|
|
||||||
String content = formData.getFirst("content");
|
|
||||||
|
|
||||||
if (title.contains(":")) {
|
|
||||||
return Response.temporaryRedirect(URI.create("/edit/" + title)).status(Response.Status.SEE_OTHER).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Page.findByTitle(title) != null) {
|
|
||||||
return Response.status(Response.Status.CONFLICT).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Page page = new Page();
|
|
||||||
page.title = title;
|
|
||||||
|
|
||||||
Account account = new Account("test user #" + new Random().nextInt(0, 1000));
|
|
||||||
|
|
||||||
PageRevision revision = new PageRevision();
|
|
||||||
revision.author = account;
|
|
||||||
revision.content = content;
|
|
||||||
revision.delta = content.length();
|
|
||||||
|
|
||||||
revision.page = page;
|
|
||||||
page.revisions.add(revision);
|
|
||||||
page.latestRevision = revision;
|
|
||||||
|
|
||||||
account.persist();
|
|
||||||
page.persist();
|
|
||||||
revision.persistAndFlush(); // this stalled me for a few hours... should move that to @Transactional
|
|
||||||
|
|
||||||
return redirectService.page(page).status(RestResponse.Status.SEE_OTHER).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/edit")
|
|
||||||
@Transactional // TODO make this in a service or whatever is it called
|
|
||||||
public Response edit(MultivaluedMap<String, String> formData) {
|
|
||||||
String title = formData.getFirst("title");
|
|
||||||
String content = formData.getFirst("content");
|
|
||||||
|
|
||||||
Page page = Page.findByTitle(title);
|
|
||||||
|
|
||||||
if (Page.findByTitle(title) == null) {
|
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Account account = new Account("test user #" + new Random().nextInt(0, 1000));
|
|
||||||
|
|
||||||
PageRevision revision = new PageRevision();
|
|
||||||
revision.index = page.latestRevision.index + 1;
|
|
||||||
revision.author = account;
|
|
||||||
revision.content = content;
|
|
||||||
revision.delta = content.length() - page.latestRevision.content.length(); // TODO optimize
|
|
||||||
|
|
||||||
revision.page = page;
|
|
||||||
page.revisions.add(revision);
|
|
||||||
page.latestRevision = revision;
|
|
||||||
|
|
||||||
account.persist();
|
|
||||||
page.persist();
|
|
||||||
revision.persistAndFlush(); // this stalled me for a few hours
|
|
||||||
|
|
||||||
return redirectService.page(page).status(RestResponse.Status.SEE_OTHER).build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,15 +29,19 @@ public class EditResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/{title}")
|
@Path("/{title:.+}")
|
||||||
public Response editPage(@PathParam("title") String title) {
|
public Response editPage(@PathParam("title") String title, @CookieParam("prefilledContent") String prefilledContent) {
|
||||||
Page page = Page.findByTitle(title);
|
Page page = Page.findByTitle(title);
|
||||||
|
|
||||||
if (page == null)
|
if (page == null)
|
||||||
return Response.status(Response.Status.NOT_FOUND).entity(Templates.create(title, "198.51.100.42")).build();
|
return Response.ok().entity(Templates.create(title, "198.51.100.42")).build();
|
||||||
|
|
||||||
|
if (prefilledContent == null || prefilledContent.isBlank()) {
|
||||||
|
prefilledContent = page.latestRevision.content;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO check for permissions
|
// TODO check for permissions
|
||||||
|
|
||||||
return Response.ok().entity(Templates.edit(page, page.latestRevision.content, "198.51.100.42")).build();
|
return Response.ok().entity(Templates.edit(page, prefilledContent, "198.51.100.42")).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class HistoryResource {
|
||||||
if (page == null)
|
if (page == null)
|
||||||
return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(title, redirectService.titleEncoded(title))).build(); // TODO replace with own template
|
return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(title, redirectService.titleEncoded(title))).build(); // TODO replace with own template
|
||||||
|
|
||||||
List<PageRevision> revisions = page.revisions;
|
List<PageRevision> revisions = page.revisions.reversed();
|
||||||
|
|
||||||
return Response.ok().entity(Templates.history(page, revisions)).build();
|
return Response.ok().entity(Templates.history(page, revisions)).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.m724.talkpages.page;
|
package eu.m724.talkpages.page;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.TemplateExtensions;
|
||||||
import eu.m724.talkpages.RedirectService;
|
import eu.m724.talkpages.RedirectService;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.Page;
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
import eu.m724.talkpages.orm.entity.PageRevision;
|
||||||
|
@ -10,6 +11,11 @@ import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Path("/page")
|
@Path("/page")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
public class PageResource {
|
public class PageResource {
|
||||||
|
@ -20,7 +26,7 @@ public class PageResource {
|
||||||
public static class Templates {
|
public static class Templates {
|
||||||
public static native TemplateInstance page(Page page, PageRevision revision, boolean old);
|
public static native TemplateInstance page(Page page, PageRevision revision, boolean old);
|
||||||
public static native TemplateInstance revisionNotFound(Page page, int revisionId);
|
public static native TemplateInstance revisionNotFound(Page page, int revisionId);
|
||||||
public static native TemplateInstance notFound(String title);
|
public static native TemplateInstance notFound(String title, List<Page> suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -31,15 +37,25 @@ public class PageResource {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/{pageId}")
|
@Path("/{pageId}")
|
||||||
public Response viewPage(@PathParam("pageId") String pageId, @QueryParam("revision") Integer revisionId) {
|
public Response viewPage(@PathParam("pageId") String pageId, @QueryParam("revision") Integer revisionId, @QueryParam("redirectFrom") String redirectFrom) {
|
||||||
System.out.println(pageId);
|
System.out.println(pageId);
|
||||||
Page page = Page.findByTitle(pageId);
|
Page page = Page.findByTitle(pageId);
|
||||||
|
|
||||||
if (page == null)
|
if (page == null) {
|
||||||
return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(pageId)).build();
|
List<Page> suggestions = Page.findByTitleIgnoreCase(pageId);
|
||||||
|
if (suggestions.size() == 1) {
|
||||||
|
return redirectService.page(suggestions.getFirst()).build();
|
||||||
|
}
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(pageId, suggestions)).build();
|
||||||
|
}
|
||||||
|
|
||||||
if (revisionId == null) {
|
if (revisionId == null) {
|
||||||
|
if (page.latestRevision.content.startsWith("@")) {
|
||||||
|
String target = page.latestRevision.content.substring(1);
|
||||||
|
if (redirectFrom == null)
|
||||||
|
redirectFrom = pageId;
|
||||||
|
return Response.temporaryRedirect(URI.create("/page/" + redirectService.titleEncoded(target) + "?redirectFrom=" + URLEncoder.encode(redirectFrom, StandardCharsets.UTF_8))).build(); // TODO I could really reduce some of this
|
||||||
|
}
|
||||||
return Response.ok().entity(Templates.page(page, page.latestRevision, false)).build();
|
return Response.ok().entity(Templates.page(page, page.latestRevision, false)).build();
|
||||||
} else {
|
} else {
|
||||||
System.out.printf("History for page: %s %d\n", page.title, page.latestRevision.index);
|
System.out.printf("History for page: %s %d\n", page.title, page.latestRevision.index);
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package eu.m724.talkpages.page.action;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.RedirectService;
|
||||||
|
import eu.m724.talkpages.orm.entity.Account;
|
||||||
|
import eu.m724.talkpages.orm.entity.Page;
|
||||||
|
import eu.m724.talkpages.orm.entity.PageRevision;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
|
import jakarta.ws.rs.core.NewCookie;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import org.jboss.resteasy.reactive.RestResponse;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Path("/action")
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
public class ActionResource {
|
||||||
|
@Inject
|
||||||
|
ActionService actionService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RedirectService redirectService;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/create")
|
||||||
|
@Transactional // TODO make this in a service or whatever is it called
|
||||||
|
public Response create(MultivaluedMap<String, String> formData) {
|
||||||
|
String title = formData.getFirst("title");
|
||||||
|
String content = formData.getFirst("content");
|
||||||
|
|
||||||
|
Account account = new Account("test user #" + new Random().nextInt(10000));
|
||||||
|
account.persistAndFlush();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Page page = actionService.createPage(title, content, account);
|
||||||
|
return redirectService.page(page)
|
||||||
|
.cookie(new NewCookie.Builder("prefilledContent").value("").maxAge(0).build())
|
||||||
|
.status(RestResponse.Status.SEE_OTHER).build();
|
||||||
|
} catch (IllegalArgumentException e) { // TODO I could reduce all this code
|
||||||
|
// illegal title
|
||||||
|
String encodedMessage = URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8);
|
||||||
|
return Response
|
||||||
|
.temporaryRedirect(URI.create("/edit/" + title + "?errorType=1&error=" + encodedMessage))
|
||||||
|
.cookie(new NewCookie.Builder("prefilledContent").path("/edit/" + title).value(content).build()) // TODO find a better way
|
||||||
|
.status(Response.Status.SEE_OTHER).build();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// page already exists
|
||||||
|
String encodedMessage = URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8);
|
||||||
|
return Response
|
||||||
|
.temporaryRedirect(URI.create("/edit/" + title + "?errorType=2&error=" + encodedMessage))
|
||||||
|
.cookie(new NewCookie.Builder("prefilledContent").path("/edit/" + title).value(content).build())
|
||||||
|
.status(Response.Status.SEE_OTHER).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return Response
|
||||||
|
.temporaryRedirect(URI.create("/edit/" + title + "?errorType=0&error=Server+error"))
|
||||||
|
.cookie(new NewCookie.Builder("prefilledContent").path("/edit/" + title).value(content).build())
|
||||||
|
.status(Response.Status.SEE_OTHER).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/edit")
|
||||||
|
@Transactional // TODO make this in a service or whatever is it called
|
||||||
|
public Response edit(MultivaluedMap<String, String> formData) {
|
||||||
|
String title = formData.getFirst("title");
|
||||||
|
String content = formData.getFirst("content");
|
||||||
|
Account account = new Account("test user #" + new Random().nextInt(0, 1000));
|
||||||
|
|
||||||
|
Page page = Page.findByTitle(title);
|
||||||
|
|
||||||
|
if (Page.findByTitle(title) == null) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
page = actionService.editPage(page, content, account);
|
||||||
|
|
||||||
|
return redirectService.page(page).status(RestResponse.Status.SEE_OTHER).build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package eu.m724.talkpages.page.action;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.orm.entity.Account;
|
||||||
|
import eu.m724.talkpages.orm.entity.Page;
|
||||||
|
import eu.m724.talkpages.orm.entity.PageRevision;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ActionService {
|
||||||
|
@Transactional
|
||||||
|
Page createPage(String title, String content, Account account) {
|
||||||
|
// title and content is sanitized so only prohibit if necessary
|
||||||
|
if (title.contains("/")) {
|
||||||
|
throw new IllegalArgumentException("Title cannot contain slashes (/). Those are used for sub-pages.");
|
||||||
|
} else if (Page.findByTitle(title) != null) {
|
||||||
|
throw new IllegalStateException("Page already exists, I made you edit it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Page page = new Page(title);
|
||||||
|
page.persist();
|
||||||
|
|
||||||
|
PageRevision revision = new PageRevision(page);
|
||||||
|
revision.author = account;
|
||||||
|
revision.content = content;
|
||||||
|
revision.delta = content.length();
|
||||||
|
|
||||||
|
page.setLatestRevision(revision);
|
||||||
|
account.revisions.add(revision);
|
||||||
|
|
||||||
|
revision.persistAndFlush();
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
Page editPage(Page page, String newContent, Account account) {
|
||||||
|
// constraints are not checked
|
||||||
|
|
||||||
|
PageRevision revision = new PageRevision(page);
|
||||||
|
revision.author = account;
|
||||||
|
revision.content = newContent;
|
||||||
|
revision.delta = newContent.length() - page.latestRevision.content.length(); // TODO optimize
|
||||||
|
|
||||||
|
page.setLatestRevision(revision);
|
||||||
|
account.revisions.add(revision);
|
||||||
|
|
||||||
|
revision.persistAndFlush();
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,3 +11,7 @@
|
||||||
<br>
|
<br>
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{#if http:param("error") != null}
|
||||||
|
<p>Error: <strong>{http:param("error")}</strong></p>
|
||||||
|
{/if}
|
|
@ -8,3 +8,7 @@
|
||||||
<br>
|
<br>
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{#if http:param("error") != null}
|
||||||
|
<p>Error: <strong>{http:param("error")}</strong></p>
|
||||||
|
{/if}
|
|
@ -1,7 +1,11 @@
|
||||||
<h1>History of {page.title}</h1>
|
<h1>History of {page.title}</h1>
|
||||||
|
|
||||||
{#for revision in revisions}
|
{#for revision in revisions}
|
||||||
|
{#if page.latestRevision == revision}
|
||||||
|
<span><strong><a href="/page/{page.title}?revision={revision.index}">#{revision.index}</a> ({revision.delta}) {revision.timestamp.toString()} by <a href="/user/{revision.author.id}">{revision.author.name}</a></strong></span>
|
||||||
|
{#else}
|
||||||
<span><a href="/page/{page.title}?revision={revision.index}">#{revision.index}</a> ({revision.delta}) {revision.timestamp.toString()} by <a href="/user/{revision.author.id}">{revision.author.name}</a></span>
|
<span><a href="/page/{page.title}?revision={revision.index}">#{revision.index}</a> ({revision.delta}) {revision.timestamp.toString()} by <a href="/user/{revision.author.id}">{revision.author.name}</a></span>
|
||||||
|
{/if}
|
||||||
<br>
|
<br>
|
||||||
{/for}
|
{/for}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
<h1>Search</h1>
|
|
||||||
<form action="/search">
|
<form action="/search">
|
||||||
<label for="query">Query</label>
|
<input type="text" name="query" id="query" placeholder="Search query">
|
||||||
<input type="text" name="query" id="query">
|
<input type="submit" value="Search">
|
||||||
<br>
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/edit">Create a page</a></li>
|
<li><a href="/edit">Create a page</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<small>Running <a href="/page/TalkPages">TalkPages</a> version {config:["quarkus.application.version"]}</small>
|
|
@ -1,6 +1,16 @@
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
|
||||||
There is no such article.
|
<p>There is no such article.</p>
|
||||||
|
|
||||||
|
{#if !suggestions.isEmpty()}
|
||||||
|
<p>Are you looking for:</p>
|
||||||
|
<ul>
|
||||||
|
{#for suggestion in suggestions}
|
||||||
|
<li><a href="/page/{suggestion.path}">{suggestion.title}</a></li>
|
||||||
|
{/for}
|
||||||
|
</ul>
|
||||||
|
<p>Actions:</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/edit/{title}">Create</a></li>
|
<li><a href="/edit/{title}">Create</a></li>
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
|
{#if http:param("redirectFrom") != null}
|
||||||
|
<p>Redirected from {http:param("redirectFrom")}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if old}
|
{#if old}
|
||||||
|
{#if page.latestRevision != revision}
|
||||||
<h3>
|
<h3>
|
||||||
You are viewing an outdated revision #{revision.index} of this page from {revision.timestamp.toString()}, authored by {revision.author.name}.
|
You are viewing an outdated revision #{revision.index} of this page from {revision.timestamp.toString()}, authored by {revision.author.name}.
|
||||||
<a href="/page/{page.title}">Go back to current version</a>
|
<br>
|
||||||
|
<a href="/page/{page.title}">See current version</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
{#else}
|
||||||
|
<h4>This is the current revision #{revision.index} authored by {revision.author.name}.</h4>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h1>{page.title}</h1>
|
<h1>{page.title}</h1>
|
||||||
|
|
||||||
{revision.content}
|
{revision.content.sanitizeContent.raw}
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue