diff --git a/src/main/java/eu/m724/talkpages/Startup.java b/src/main/java/eu/m724/talkpages/Startup.java
index ca0cf0c..50d7020 100644
--- a/src/main/java/eu/m724/talkpages/Startup.java
+++ b/src/main/java/eu/m724/talkpages/Startup.java
@@ -22,9 +22,6 @@ public class Startup {
@ConfigProperty(name = "talkpages.systemUser.name")
private String username;
- @ConfigProperty(name = "talkpages.createExamplePage")
- private boolean createExamplePage;
-
void installStaticRoute(@Observes StartupEvent startupEvent, Router router) {
router.route()
.path("/static/*")
@@ -33,7 +30,7 @@ public class Startup {
@Transactional
public void examplePage(@Observes StartupEvent ignoredEvent) {
- if (Account.findById(1) != null) {
+ if (Account.findByName(username) != null) {
// system user exists so assuming this is not the first run
return;
}
@@ -43,15 +40,18 @@ public class Startup {
Account account = new Account(username);
account.persistAndFlush();
- if (createExamplePage) {
- addPage(account, "TalkPages", "
A website where the users collaboratively create content
");
- addPage(account, "Talkpages", "ambiguous for [TalkPages]");
- addPage(account, "TP", "@TalkPages");
- }
+ Page talkPagesPage = addPage(account, "TalkPages", "A website where the users collaboratively create content
");
+ addPage(account, "Talkpages", "ambiguous for [TalkPages]");
+ addPage(account, "TP", "@TalkPages");
+
+ Page tosPage = addPage(account, "Terms of Service", "TODO");
+ addPage(account, "ToS", "@TalkPages/Terms of Service");
+
+ tosPage.setParentPage(talkPagesPage);
}
@Transactional
- public void addPage(Account account, String title, String content) {
+ public Page addPage(Account account, String title, String content) {
Page page = new Page(title);
PageRevision revision = new PageRevision(page, account, content);
@@ -59,6 +59,6 @@ public class Startup {
page.persistAndFlush();
account.getRevisions().add(revision);
-
+ return page;
}
}
diff --git a/src/main/java/eu/m724/talkpages/auth/AuthService.java b/src/main/java/eu/m724/talkpages/auth/AuthService.java
index 7c3548b..90ffe12 100644
--- a/src/main/java/eu/m724/talkpages/auth/AuthService.java
+++ b/src/main/java/eu/m724/talkpages/auth/AuthService.java
@@ -25,7 +25,7 @@ public class AuthService {
*/
@Transactional
Session register(String username, String password) throws UsernameExistsException {
- Account account = Account.findById(username);
+ Account account = Account.findByName(username);
if (account != null) {
throw new UsernameExistsException();
@@ -63,7 +63,7 @@ public class AuthService {
@Transactional
Session authenticate(String username, String password) throws InvalidCredentialsException {
- Account account = Account.findById(username);
+ Account account = Account.findByName(username);
if (account == null) {
throw new InvalidCredentialsException(false);
diff --git a/src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java b/src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java
index 8ed040d..fc1ee22 100644
--- a/src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java
+++ b/src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java
@@ -14,8 +14,13 @@ import java.util.List;
import java.util.Set;
// TODO consider moving away authentication to allow like multiple profiles on one account
+// TODO also consider giving different, namespaced ids to system and addressed accounts
@Entity
-public class Account extends PanacheEntityBase {
+@Table(
+ indexes = @Index(name = "idx_name", columnList = "name"),
+ uniqueConstraints = @UniqueConstraint(columnNames = "name")
+)
+public class Account extends PanacheEntity {
private Account() {}
/**
@@ -46,7 +51,6 @@ public class Account extends PanacheEntityBase {
// Columns
- @Id
private String name;
private String slug;
@@ -72,4 +76,11 @@ public class Account extends PanacheEntityBase {
public List getRevisions() { return revisions; }
public boolean isSystemAccount() { return password == null; }
+
+
+ // Operations
+
+ public static Account findByName(String name) {
+ return Account.find("name", name).firstResult();
+ }
}
diff --git a/src/main/java/eu/m724/talkpages/orm/entity/content/Page.java b/src/main/java/eu/m724/talkpages/orm/entity/content/Page.java
index 487cdef..3910686 100644
--- a/src/main/java/eu/m724/talkpages/orm/entity/content/Page.java
+++ b/src/main/java/eu/m724/talkpages/orm/entity/content/Page.java
@@ -1,35 +1,29 @@
package eu.m724.talkpages.orm.entity.content;
+import eu.m724.talkpages.page.action.NoSuchNamespaceException;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
-import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.*;
-import org.hibernate.annotations.GenericGenerator;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
@Entity
@Table(
indexes = @Index(name = "idx_title", columnList = "title"),
- uniqueConstraints = @UniqueConstraint(columnNames = "title")
+ uniqueConstraints = @UniqueConstraint(columnNames = {"title", "parentPage"})
)
-public class Page extends PanacheEntityBase {
+public class Page extends PanacheEntity {
private Page() {}
public Page(String title) {
- // TODO maybe map space to _
- this.slug = URLEncoder.encode(title, StandardCharsets.UTF_8).replace("+", "%20");
this.title = title;
}
// Columns
- @Id
- private String slug;
-
- // TODO is this really necessary
private String title;
@OneToOne
@@ -38,21 +32,103 @@ public class Page extends PanacheEntityBase {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List revisions = new ArrayList<>();
+ @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "parentPage")
+ private List subpages = new ArrayList<>();
+
+ @ManyToOne
+ @JoinColumn(name = "parentPage_id")
+ private Page parentPage = null;
+
+
+ // Hooks
+
+ @PreRemove
+ private void preRemove() {
+ subpages.forEach(page -> page.parentPage = null);
+ }
+
// Getters
- public String getSlug() { return slug; }
public String getTitle() { return title; }
public PageRevision getLatestRevision() { return latestRevision; }
public List getRevisions() { return revisions; }
+ public List getSubpages() { return subpages; }
+ public Page getParentPage() { return parentPage; }
public void setLatestRevision(PageRevision pageRevision) {
revisions.add(pageRevision);
latestRevision = pageRevision;
}
+ public void setParentPage(Page parentPage) {
+ // Remove from current parent, if any
+ if (this.parentPage != null)
+ parentPage.subpages.remove(this);
+
+ // Set the parent page and add as a child
+ this.parentPage = parentPage;
+ if (parentPage != null)
+ parentPage.getSubpages().add(this);
+ }
+
+ public String getSlug() {
+ // TODO maybe map space to _
+ return URLEncoder.encode(title, StandardCharsets.UTF_8).replace("+", "%20");
+ }
+
+ /**
+ * Chain of parents. The broadest (root) one is the first one.
+ * The list is empty if there's no parents.
+ *
+ * @return chain of parents
+ */
+ public List getChain() {
+ List parents = new ArrayList<>();
+
+ Page parent = this.getParentPage();
+ while (parent != null) {
+ parents.add(parent);
+ parent = parent.getParentPage();
+ }
+
+ return parents.reversed();
+ }
+
// Operations
+ /**
+ * Find a {@link Page} by title. The title must not be encoded.
+ * Separate namespaces with a slash (/
)
+ *
+ * @param title the title, not encoded
+ * @return page if found, else null
+ * @throws IllegalStateException if
+ */
+ public static Page findByPath(String title) {
+ String[] parts = title.split("/", -1);
+
+ Page page = findByTitle(parts[0]);
+
+ for (int i=1; i p.getTitle().equals(part))
+ .findFirst().orElse(null); // TODO optimize
+ }
+
+ return page;
+ }
+
public static Page findByTitle(String title) {
return find("title", title).firstResult();
}
diff --git a/src/main/java/eu/m724/talkpages/orm/entity/content/PageRevision.java b/src/main/java/eu/m724/talkpages/orm/entity/content/PageRevision.java
index 44fc68a..37f8ebd 100644
--- a/src/main/java/eu/m724/talkpages/orm/entity/content/PageRevision.java
+++ b/src/main/java/eu/m724/talkpages/orm/entity/content/PageRevision.java
@@ -9,7 +9,7 @@ import java.time.LocalDateTime;
@Entity
@Table(
- indexes = @Index(columnList = "page_slug"),
+ indexes = @Index(columnList = "page_id"),
uniqueConstraints = @UniqueConstraint(columnNames = {"id", "index"})
)
public class PageRevision extends PanacheEntity {
@@ -49,7 +49,7 @@ public class PageRevision extends PanacheEntity {
private Account author;
// TODO wondering about a table only for content and meta and title perhaps
- @Column(columnDefinition="text")
+ @Column(columnDefinition = "text")
private String content;
private int delta;
diff --git a/src/main/java/eu/m724/talkpages/page/HistoryResource.java b/src/main/java/eu/m724/talkpages/page/HistoryResource.java
index 7149d46..5379384 100644
--- a/src/main/java/eu/m724/talkpages/page/HistoryResource.java
+++ b/src/main/java/eu/m724/talkpages/page/HistoryResource.java
@@ -34,7 +34,7 @@ public class HistoryResource {
}
@GET
- @Path("/{title}")
+ @Path("/{title:.+}")
public Response pageHistory(@PathParam("title") String title) {
Page page = Page.findByTitle(title);
diff --git a/src/main/java/eu/m724/talkpages/page/PageResource.java b/src/main/java/eu/m724/talkpages/page/PageResource.java
index 16c6da9..43067b8 100644
--- a/src/main/java/eu/m724/talkpages/page/PageResource.java
+++ b/src/main/java/eu/m724/talkpages/page/PageResource.java
@@ -35,16 +35,16 @@ public class PageResource {
}
@GET
- @Path("/{pageId}")
- public Response viewPage(@PathParam("pageId") String pageId, @QueryParam("revision") Integer revisionId, @QueryParam("redirectFrom") String redirectFrom) {
- Page page = Page.findByTitle(pageId);
+ @Path("/{title:.+}")
+ public Response viewPage(@PathParam("title") String title, @QueryParam("revision") Integer revisionId, @QueryParam("redirectFrom") String redirectFrom) {
+ Page page = Page.findByPath(title);
if (page == null) {
- List suggestions = Page.findByTitleIgnoreCase(pageId);
+ List suggestions = Page.findByTitleIgnoreCase(title);
if (suggestions.size() == 1) {
return redirectService.page(suggestions.getFirst()).build();
}
- return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(pageId, suggestions)).build();
+ return Response.status(Response.Status.NOT_FOUND).entity(Templates.notFound(title, suggestions)).build();
}
if (revisionId == null) {
@@ -52,7 +52,7 @@ public class PageResource {
if (revision.getContent().startsWith("@")) {
String target = revision.getContent().substring(1);
if (redirectFrom == null)
- redirectFrom = pageId;
+ redirectFrom = title;
return Response.temporaryRedirect(URI.create("/page/" + redirectService.titleEncoded(target) + "?redirectFrom=" + URLEncoder.encode(redirectFrom, StandardCharsets.UTF_8))).build();
}
return Response.ok().entity(Templates.page(page, page.getLatestRevision(), false)).build();
diff --git a/src/main/java/eu/m724/talkpages/page/action/NoSuchNamespaceException.java b/src/main/java/eu/m724/talkpages/page/action/NoSuchNamespaceException.java
new file mode 100644
index 0000000..537b4c3
--- /dev/null
+++ b/src/main/java/eu/m724/talkpages/page/action/NoSuchNamespaceException.java
@@ -0,0 +1,9 @@
+package eu.m724.talkpages.page.action;
+
+public class NoSuchNamespaceException extends RuntimeException {
+ public final String namespace;
+
+ public NoSuchNamespaceException(String namespace) {
+ this.namespace = namespace;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index d5d713d..d0e7e4d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,10 +1,9 @@
talkpages.homePage=/
talkpages.systemUser.name=System
-talkpages.createExamplePage=true
quarkus.http.auth.basic=true
-quarkus.hibernate-orm.database.generation=drop-and-create
+quarkus.hibernate-orm.database.generation=update
quarkus.datasource.db-kind=h2
quarkus.datasource.username=username-default
diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/templates/PageResource/page.html b/src/main/resources/templates/PageResource/page.html
index cf3ca9b..a43ed37 100644
--- a/src/main/resources/templates/PageResource/page.html
+++ b/src/main/resources/templates/PageResource/page.html
@@ -1,4 +1,4 @@
-{#include layout}
+{#include layout customTitle=true noHeading=true}
{#header}
{#if http:param("redirectFrom") != null}
Redirected from {http:param("redirectFrom")}
@@ -15,9 +15,23 @@
This is the current revision #{revision.index} authored by {revision.author.name}.
{/if}
{/if}
+
{/header}
- {#pageTitle}{page.title}{/pageTitle}
+ {#pageTitle}{page.getTitle} - TalkPages{/pageTitle}
+
+ {#if page.getChain.isEmpty}
+ {page.getTitle}
+ {#else}
+
+
+ {#for parent in page.getChain}
+ {parent.getTitle} /
+ {/for}
+
+ {page.getTitle}
+
+ {/if}
{revision.content.sanitizeContent.raw}
diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html
index 0b6038d..5135d35 100644
--- a/src/main/resources/templates/layout.html
+++ b/src/main/resources/templates/layout.html
@@ -4,6 +4,7 @@
+
{#if theme:darkTheme}
{/if}
@@ -11,7 +12,10 @@
{#insert header}{/}
- {#insert pageTitle /}
+
+ {#if !noHeading??}
+ {#insert pageTitle /}
+ {/if}
{#insert}{/}