huge refactoring
fixed everything except for complicated hibernate stuff
This commit is contained in:
parent
20d0e142c0
commit
9ef47b2833
29 changed files with 434 additions and 355 deletions
3
TODO.md
Normal file
3
TODO.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Review:
|
||||||
|
- `@Transactional`
|
||||||
|
- `.persist()` (especially `.persistAndFlush()`)
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.m724.talkpages;
|
package eu.m724.talkpages;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
@ -19,7 +19,7 @@ public class RedirectService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response.ResponseBuilder page(Page page) {
|
public Response.ResponseBuilder page(Page page) {
|
||||||
return redirect("/page/" + page.path);
|
return redirect("/page/" + page.getSlug());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response.ResponseBuilder pageTitle(String title) {
|
public Response.ResponseBuilder pageTitle(String title) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package eu.m724.talkpages;
|
package eu.m724.talkpages;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Account;
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
import eu.m724.talkpages.orm.entity.content.PageRevision;
|
||||||
import io.quarkus.runtime.LaunchMode;
|
import io.quarkus.runtime.LaunchMode;
|
||||||
import io.quarkus.runtime.StartupEvent;
|
import io.quarkus.runtime.StartupEvent;
|
||||||
import jakarta.enterprise.event.Observes;
|
import jakarta.enterprise.event.Observes;
|
||||||
|
@ -33,7 +33,6 @@ public class Startup {
|
||||||
System.out.println("Performing first run setup");
|
System.out.println("Performing first run setup");
|
||||||
|
|
||||||
Account account = new Account(username);
|
Account account = new Account(username);
|
||||||
//account.id = 0L;
|
|
||||||
account.persistAndFlush();
|
account.persistAndFlush();
|
||||||
|
|
||||||
if (createExamplePage) {
|
if (createExamplePage) {
|
||||||
|
@ -46,15 +45,12 @@ public class Startup {
|
||||||
@Transactional
|
@Transactional
|
||||||
public void addPage(Account account, String title, String content) {
|
public void addPage(Account account, String title, String content) {
|
||||||
Page page = new Page(title);
|
Page page = new Page(title);
|
||||||
|
PageRevision revision = new PageRevision(page, account, content);
|
||||||
PageRevision revision = new PageRevision(page);
|
|
||||||
revision.author = account;
|
|
||||||
revision.content = content;
|
|
||||||
revision.delta = revision.content.length();
|
|
||||||
|
|
||||||
page.setLatestRevision(revision);
|
page.setLatestRevision(revision);
|
||||||
account.revisions.add(revision);
|
page.persistAndFlush();
|
||||||
|
|
||||||
|
account.getRevisions().add(revision);
|
||||||
|
|
||||||
revision.persistAndFlush();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.m724.talkpages.auth;
|
package eu.m724.talkpages.auth;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Session;
|
import eu.m724.talkpages.orm.entity.auth.Session;
|
||||||
import io.quarkus.qute.CheckedTemplate;
|
import io.quarkus.qute.CheckedTemplate;
|
||||||
import io.quarkus.qute.TemplateInstance;
|
import io.quarkus.qute.TemplateInstance;
|
||||||
import io.quarkus.security.Authenticated;
|
import io.quarkus.security.Authenticated;
|
||||||
|
@ -12,7 +12,6 @@ import jakarta.ws.rs.core.*;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Path("/auth")
|
@Path("/auth")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@ -73,7 +72,7 @@ public class AuthResource {
|
||||||
Session session = authService.authenticate(username, password);
|
Session session = authService.authenticate(username, password);
|
||||||
|
|
||||||
NewCookie cookie = new NewCookie.Builder("session-token")
|
NewCookie cookie = new NewCookie.Builder("session-token")
|
||||||
.value(session.token)
|
.value(session.getToken())
|
||||||
.maxAge(604800)// TODO make the age respect session's expires
|
.maxAge(604800)// TODO make the age respect session's expires
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.path("/")
|
.path("/")
|
||||||
|
@ -124,7 +123,7 @@ public class AuthResource {
|
||||||
Session session = authService.register(username, password);
|
Session session = authService.register(username, password);
|
||||||
|
|
||||||
NewCookie cookie = new NewCookie.Builder("session-token")
|
NewCookie cookie = new NewCookie.Builder("session-token")
|
||||||
.value(session.token)
|
.value(session.getToken())
|
||||||
.maxAge(604800)// TODO make the age respect session's expires
|
.maxAge(604800)// TODO make the age respect session's expires
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.path("/")
|
.path("/")
|
||||||
|
|
|
@ -2,8 +2,8 @@ package eu.m724.talkpages.auth;
|
||||||
|
|
||||||
import de.mkammerer.argon2.Argon2;
|
import de.mkammerer.argon2.Argon2;
|
||||||
import de.mkammerer.argon2.Argon2Factory;
|
import de.mkammerer.argon2.Argon2Factory;
|
||||||
import eu.m724.talkpages.orm.entity.Session;
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
import eu.m724.talkpages.orm.entity.RegisteredUser;
|
import eu.m724.talkpages.orm.entity.auth.Session;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ public class AuthService {
|
||||||
private final SecureRandom random = new SecureRandom();
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new {@link RegisteredUser}
|
* Register a new {@link Account}
|
||||||
*
|
*
|
||||||
* @param username username
|
* @param username username
|
||||||
* @param password password
|
* @param password password
|
||||||
|
@ -25,16 +25,20 @@ public class AuthService {
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
Session register(String username, String password) throws UsernameExistsException {
|
Session register(String username, String password) throws UsernameExistsException {
|
||||||
RegisteredUser user = RegisteredUser.find("username", username).firstResult();
|
Account account = Account.findById(username);
|
||||||
|
|
||||||
if (user != null) {
|
if (account != null) {
|
||||||
throw new UsernameExistsException();
|
throw new UsernameExistsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
String hashedPassword = argon2.hash(10, 65536, 1, password);
|
String hashedPassword = argon2.hash(10, 65536, 1, password);
|
||||||
user = RegisteredUser.add(username, hashedPassword, "user");
|
account = new Account(username, hashedPassword);
|
||||||
|
account.persistAndFlush();
|
||||||
|
|
||||||
return Session.createForUser(user);
|
Session session = new Session(account);
|
||||||
|
session.persist();
|
||||||
|
|
||||||
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -47,7 +51,7 @@ public class AuthService {
|
||||||
Session session = Session.find("token", sessionToken).firstResult();
|
Session session = Session.find("token", sessionToken).firstResult();
|
||||||
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
if (session.expires.isAfter(LocalDateTime.now())) {
|
if (session.getExpires().isAfter(LocalDateTime.now())) {
|
||||||
return session;
|
return session;
|
||||||
} else {
|
} else {
|
||||||
session.delete();
|
session.delete();
|
||||||
|
@ -59,17 +63,20 @@ public class AuthService {
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
Session authenticate(String username, String password) throws InvalidCredentialsException {
|
Session authenticate(String username, String password) throws InvalidCredentialsException {
|
||||||
RegisteredUser user = RegisteredUser.find("username", username).firstResult();
|
Account account = Account.findById(username);
|
||||||
|
|
||||||
if (user == null) {
|
if (account == null) {
|
||||||
throw new InvalidCredentialsException(false);
|
throw new InvalidCredentialsException(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argon2.verify(user.password, password)) {
|
if (!argon2.verify(account.getPassword(), password)) {
|
||||||
throw new InvalidCredentialsException(true);
|
throw new InvalidCredentialsException(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Session.createForUser(user);
|
Session session = new Session(account);
|
||||||
|
session.persist();
|
||||||
|
|
||||||
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InvalidCredentialsException extends Exception {
|
public static class InvalidCredentialsException extends Exception {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package eu.m724.talkpages.auth;
|
package eu.m724.talkpages.auth;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Session;
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
import eu.m724.talkpages.orm.entity.RegisteredUser;
|
import eu.m724.talkpages.orm.entity.auth.Session;
|
||||||
import io.quarkus.security.identity.IdentityProviderManager;
|
import io.quarkus.security.identity.IdentityProviderManager;
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
import io.quarkus.security.runtime.QuarkusPrincipal;
|
import io.quarkus.security.runtime.QuarkusPrincipal;
|
||||||
|
@ -25,27 +25,25 @@ public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanis
|
||||||
AuthService authService;
|
AuthService authService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
|
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { // TODO handle ip accounts
|
||||||
|
|
||||||
return Uni.createFrom().item(() -> {
|
return Uni.createFrom().item(() -> {
|
||||||
Cookie cookie = context.request().getCookie("session-token");
|
Cookie cookie = context.request().getCookie("session-token");
|
||||||
|
|
||||||
if (cookie == null)
|
if (cookie != null) {
|
||||||
return (SecurityIdentity) QuarkusSecurityIdentity.builder().setAnonymous(true).build();
|
String sessionToken = context.request().getCookie("session-token").getValue();
|
||||||
|
Session session = authService.validateSessionToken(sessionToken);
|
||||||
|
|
||||||
String sessionToken = context.request().getCookie("session-token").getValue();
|
if (session != null) {
|
||||||
Session session = authService.validateSessionToken(sessionToken);
|
Account account = session.getAccount();
|
||||||
|
|
||||||
if (session != null) {
|
QuarkusSecurityIdentity identity = QuarkusSecurityIdentity.builder()
|
||||||
RegisteredUser user = session.user;
|
.setPrincipal(new QuarkusPrincipal(account.getName()))
|
||||||
|
.addRoles(account.getRoles())
|
||||||
|
.addAttribute("session", session)
|
||||||
|
.build();
|
||||||
|
|
||||||
QuarkusSecurityIdentity identity = QuarkusSecurityIdentity.builder()
|
return (SecurityIdentity) identity;
|
||||||
.setPrincipal(new QuarkusPrincipal(user.username))
|
}
|
||||||
.addRoles(user.roles)
|
|
||||||
.addAttribute("session", session)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return (SecurityIdentity) identity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (SecurityIdentity) QuarkusSecurityIdentity.builder().setAnonymous(true).build();
|
return (SecurityIdentity) QuarkusSecurityIdentity.builder().setAnonymous(true).build();
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.entity;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class Account extends PanacheEntity {
|
|
||||||
public Account() {}
|
|
||||||
|
|
||||||
public Account(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the name of this account
|
|
||||||
*/
|
|
||||||
@Column(unique = true)
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This user's edits
|
|
||||||
*/
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = false)
|
|
||||||
public List<PageRevision> revisions = new ArrayList<>();
|
|
||||||
|
|
||||||
@OneToOne
|
|
||||||
public RegisteredUser user;
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.entity;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class Page extends PanacheEntityBase {
|
|
||||||
public Page() {}
|
|
||||||
|
|
||||||
public Page(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(generator = "title-path")
|
|
||||||
@GenericGenerator(name = "title-path", strategy = "eu.m724.talkpages.orm.generator.PathGenerator")
|
|
||||||
public String path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The title of this page, derives path
|
|
||||||
*/
|
|
||||||
@Column(unique = true)
|
|
||||||
public String title;
|
|
||||||
|
|
||||||
@OneToOne
|
|
||||||
public PageRevision latestRevision;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revisions of this page
|
|
||||||
*/
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
|
||||||
public List<PageRevision> revisions = new ArrayList<>();
|
|
||||||
|
|
||||||
public static Page findByTitle(String title) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.entity;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
uniqueConstraints = @UniqueConstraint(columnNames = {"id", "index"})
|
|
||||||
)
|
|
||||||
public class PageRevision extends PanacheEntity {
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the index of the revision for a page, first revision is 1
|
|
||||||
* TODO make generated
|
|
||||||
*/
|
|
||||||
public long index = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The creation time of the revision
|
|
||||||
*/
|
|
||||||
@CreationTimestamp
|
|
||||||
public LocalDateTime timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The author of the revision
|
|
||||||
*/
|
|
||||||
@ManyToOne(cascade = CascadeType.ALL)
|
|
||||||
public Account author;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The full page content of the revision.
|
|
||||||
*/
|
|
||||||
@Column(columnDefinition="text")
|
|
||||||
public String content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change of content size compared to previous revision<br>
|
|
||||||
* If this is the first revision, total bytes
|
|
||||||
*/
|
|
||||||
public int delta;
|
|
||||||
|
|
||||||
public static PageRevision findByIndex(Page page, long index) {
|
|
||||||
return find("page = ?1 and index = ?2", page, index).firstResult();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.entity;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class RegisteredUser extends PanacheEntityBase {
|
|
||||||
@Id
|
|
||||||
public String username;
|
|
||||||
|
|
||||||
public String password;
|
|
||||||
|
|
||||||
public Set<String> roles;
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = false)
|
|
||||||
public Set<Session> sessions = new HashSet<>();
|
|
||||||
|
|
||||||
@OneToOne
|
|
||||||
public Account account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new user to the database
|
|
||||||
* @param username the username
|
|
||||||
* @param password HASHED password
|
|
||||||
* @param roles roles
|
|
||||||
*/
|
|
||||||
public static RegisteredUser add(String username, String password, String... roles) {
|
|
||||||
RegisteredUser user = new RegisteredUser();
|
|
||||||
|
|
||||||
user.username = username;
|
|
||||||
user.password = password;
|
|
||||||
user.roles = Set.of(roles);
|
|
||||||
user.persistAndFlush();
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.entity;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class Session extends PanacheEntity {
|
|
||||||
@ManyToOne(cascade = CascadeType.ALL)
|
|
||||||
public RegisteredUser user;
|
|
||||||
|
|
||||||
public String token; // TODO make this generated
|
|
||||||
|
|
||||||
public LocalDateTime expires;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a {@link Session} for a {@link RegisteredUser}<br>
|
|
||||||
* randomly generated token, expiring in 7 days
|
|
||||||
*
|
|
||||||
* @param user the user
|
|
||||||
* @return the created session
|
|
||||||
*/
|
|
||||||
public static Session createForUser(RegisteredUser user) {
|
|
||||||
byte[] tokenBytes = new byte[64];
|
|
||||||
new SecureRandom().nextBytes(tokenBytes);
|
|
||||||
String token = Base64.getEncoder().encodeToString(tokenBytes);
|
|
||||||
|
|
||||||
Session session = new Session();
|
|
||||||
session.user = user;
|
|
||||||
session.token = token;
|
|
||||||
session.expires = LocalDateTime.now().plusDays(7);
|
|
||||||
user.sessions.add(session);
|
|
||||||
|
|
||||||
session.persist();
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
75
src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java
Normal file
75
src/main/java/eu/m724/talkpages/orm/entity/auth/Account.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package eu.m724.talkpages.orm.entity.auth;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.orm.entity.content.PageRevision;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
// TODO consider moving away authentication to allow like multiple profiles on one account
|
||||||
|
@Entity
|
||||||
|
public class Account extends PanacheEntityBase {
|
||||||
|
private Account() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a user account.<br>
|
||||||
|
* No default roles.
|
||||||
|
*
|
||||||
|
* @param name username
|
||||||
|
* @param password hashed password
|
||||||
|
*/
|
||||||
|
public Account(String name, String password) {
|
||||||
|
this.name = name;
|
||||||
|
this.slug = URLEncoder.encode(name, StandardCharsets.UTF_8);
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a system account.<br>
|
||||||
|
* System accounts don't have passwords, so can't be logged into.<br>
|
||||||
|
* No default roles.
|
||||||
|
*
|
||||||
|
* @param name username
|
||||||
|
*/
|
||||||
|
public Account(String name) {
|
||||||
|
this(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String slug;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
|
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
|
||||||
|
private Set<Session> sessions = new HashSet<>();
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = false, fetch = FetchType.EAGER)
|
||||||
|
private List<PageRevision> revisions = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public String getSlug() { return slug; }
|
||||||
|
public String getPassword() { return password; }
|
||||||
|
public Set<String> getRoles() { return roles; }
|
||||||
|
public Set<Session> getSessions() { return sessions; }
|
||||||
|
public List<PageRevision> getRevisions() { return revisions; }
|
||||||
|
|
||||||
|
public boolean isSystemAccount() { return password == null; }
|
||||||
|
}
|
65
src/main/java/eu/m724/talkpages/orm/entity/auth/Session.java
Normal file
65
src/main/java/eu/m724/talkpages/orm/entity/auth/Session.java
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package eu.m724.talkpages.orm.entity.auth;
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
// TODO clean old sessions
|
||||||
|
@Entity
|
||||||
|
public class Session extends PanacheEntity {
|
||||||
|
private Session() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Session} for the given {@link Account}<br>
|
||||||
|
* A token is randomly generated. It's 64 bytes and is encoded in Base64.<br>
|
||||||
|
* Expiration is set to 7 days in the future.
|
||||||
|
*
|
||||||
|
* @param account the account
|
||||||
|
*/
|
||||||
|
public Session(Account account) {
|
||||||
|
this(account, java.time.LocalDateTime.now().plusDays(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Session} for the given {@link Account}<br>
|
||||||
|
* A token is randomly generated. It's 64 bytes and is encoded in Base64.<br>
|
||||||
|
*
|
||||||
|
* @param account the account
|
||||||
|
* @param expires expiration time
|
||||||
|
*/
|
||||||
|
public Session(Account account, LocalDateTime expires) {
|
||||||
|
byte[] tokenBytes = new byte[64];
|
||||||
|
new SecureRandom().nextBytes(tokenBytes);
|
||||||
|
String token = Base64.getEncoder().encodeToString(tokenBytes);
|
||||||
|
|
||||||
|
this.account = account;
|
||||||
|
this.token = token;
|
||||||
|
this.expires = expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
|
||||||
|
@ManyToOne(cascade = CascadeType.ALL)
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
// TODO make a generator for this if possible
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
// TODO rename?
|
||||||
|
private LocalDateTime expires;
|
||||||
|
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
public Account getAccount() { return account; }
|
||||||
|
public String getToken() { return token; }
|
||||||
|
public LocalDateTime getExpires() { return expires; }
|
||||||
|
|
||||||
|
}
|
63
src/main/java/eu/m724/talkpages/orm/entity/content/Page.java
Normal file
63
src/main/java/eu/m724/talkpages/orm/entity/content/Page.java
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package eu.m724.talkpages.orm.entity.content;
|
||||||
|
|
||||||
|
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.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
indexes = @Index(name = "idx_title", columnList = "title"),
|
||||||
|
uniqueConstraints = @UniqueConstraint(columnNames = "title")
|
||||||
|
)
|
||||||
|
public class Page extends PanacheEntityBase {
|
||||||
|
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
|
||||||
|
private PageRevision latestRevision;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<PageRevision> revisions = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
public String getSlug() { return slug; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public PageRevision getLatestRevision() { return latestRevision; }
|
||||||
|
public List<PageRevision> getRevisions() { return revisions; }
|
||||||
|
|
||||||
|
public void setLatestRevision(PageRevision pageRevision) {
|
||||||
|
revisions.add(pageRevision);
|
||||||
|
latestRevision = pageRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operations
|
||||||
|
|
||||||
|
public static Page findByTitle(String title) {
|
||||||
|
return find("title", title).firstResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Page> findByTitleIgnoreCase(String title) {
|
||||||
|
return find("lower(title) = ?1", title.toLowerCase()).list();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package eu.m724.talkpages.orm.entity.content;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
indexes = @Index(columnList = "page_slug"),
|
||||||
|
uniqueConstraints = @UniqueConstraint(columnNames = {"id", "index"})
|
||||||
|
)
|
||||||
|
public class PageRevision extends PanacheEntity {
|
||||||
|
private PageRevision() {}
|
||||||
|
|
||||||
|
public PageRevision(Page page, Account author, String content) {
|
||||||
|
long index = 1;
|
||||||
|
int delta = content.length();
|
||||||
|
|
||||||
|
if (page.getLatestRevision() != null) {
|
||||||
|
index = page.getLatestRevision().index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!page.getRevisions().isEmpty()) {
|
||||||
|
delta -= page.getRevisions().getLast().content.length();
|
||||||
|
} // TODO don't
|
||||||
|
|
||||||
|
this.page = page;
|
||||||
|
this.index = index;
|
||||||
|
this.author = author;
|
||||||
|
this.content = content;
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
|
||||||
|
@ManyToOne(cascade = CascadeType.DETACH)
|
||||||
|
private Page page;
|
||||||
|
|
||||||
|
private long index = 1;
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
@ManyToOne(cascade = CascadeType.DETACH)
|
||||||
|
private Account author;
|
||||||
|
|
||||||
|
// TODO wondering about a table only for content and meta and title perhaps
|
||||||
|
@Column(columnDefinition="text")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
private int delta;
|
||||||
|
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
public Page getPage() { return page; }
|
||||||
|
public long getIndex() { return index; }
|
||||||
|
public LocalDateTime getTimestamp() { return timestamp; }
|
||||||
|
public Account getAuthor() { return author; }
|
||||||
|
public String getContent() { return content; }
|
||||||
|
public int getDelta() { return delta; }
|
||||||
|
|
||||||
|
|
||||||
|
// Operations
|
||||||
|
|
||||||
|
public static PageRevision findByIndex(Page page, long index) {
|
||||||
|
return find("page = ?1 and index = ?2", page, index).firstResult();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package eu.m724.talkpages.orm.generator;
|
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
|
||||||
import org.hibernate.HibernateException;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.id.IdentifierGenerator;
|
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class PathGenerator implements IdentifierGenerator {
|
|
||||||
@Override
|
|
||||||
public Object generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) {
|
|
||||||
if (!(o instanceof Page page)) {
|
|
||||||
throw new HibernateException("The entity must be a Page");
|
|
||||||
}
|
|
||||||
|
|
||||||
return URLEncoder.encode(page.title, StandardCharsets.UTF_8).replace("+", "%20");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.m724.talkpages.page;
|
package eu.m724.talkpages.page;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import io.quarkus.qute.CheckedTemplate;
|
import io.quarkus.qute.CheckedTemplate;
|
||||||
import io.quarkus.qute.TemplateInstance;
|
import io.quarkus.qute.TemplateInstance;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
|
@ -12,14 +12,14 @@ import jakarta.ws.rs.core.Response;
|
||||||
public class EditResource {
|
public class EditResource {
|
||||||
@CheckedTemplate
|
@CheckedTemplate
|
||||||
public static class Templates {
|
public static class Templates {
|
||||||
public static native TemplateInstance edit(Page page, String content, String name);
|
public static native TemplateInstance edit(Page page, String content);
|
||||||
public static native TemplateInstance create(String title, String name);
|
public static native TemplateInstance create(String title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/")
|
@Path("/")
|
||||||
public Response editPageBlank() {
|
public Response editPageBlank() {
|
||||||
return Response.ok().entity(Templates.create("", "198.51.100.42")).build();
|
return Response.ok().entity(Templates.create("")).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
@ -28,14 +28,14 @@ public class EditResource {
|
||||||
Page page = Page.findByTitle(title);
|
Page page = Page.findByTitle(title);
|
||||||
|
|
||||||
if (page == null)
|
if (page == null)
|
||||||
return Response.ok().entity(Templates.create(title, "198.51.100.42")).build();
|
return Response.ok().entity(Templates.create(title)).build();
|
||||||
|
|
||||||
if (prefilledContent == null || prefilledContent.isBlank()) {
|
if (prefilledContent == null || prefilledContent.isBlank()) {
|
||||||
prefilledContent = page.latestRevision.content;
|
prefilledContent = page.getLatestRevision().getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check for permissions
|
// TODO check for permissions
|
||||||
|
|
||||||
return Response.ok().entity(Templates.edit(page, prefilledContent, "198.51.100.42")).build();
|
return Response.ok().entity(Templates.edit(page, prefilledContent)).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package eu.m724.talkpages.page;
|
package eu.m724.talkpages.page;
|
||||||
|
|
||||||
import eu.m724.talkpages.RedirectService;
|
import eu.m724.talkpages.RedirectService;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
import eu.m724.talkpages.orm.entity.content.PageRevision;
|
||||||
import io.quarkus.qute.CheckedTemplate;
|
import io.quarkus.qute.CheckedTemplate;
|
||||||
import io.quarkus.qute.TemplateInstance;
|
import io.quarkus.qute.TemplateInstance;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
@ -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.reversed();
|
List<PageRevision> revisions = page.getRevisions().reversed();
|
||||||
|
|
||||||
return Response.ok().entity(Templates.history(page, revisions)).build();
|
return Response.ok().entity(Templates.history(page, revisions)).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package eu.m724.talkpages.page;
|
package eu.m724.talkpages.page;
|
||||||
|
|
||||||
import eu.m724.talkpages.RedirectService;
|
import eu.m724.talkpages.RedirectService;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
import eu.m724.talkpages.orm.entity.content.PageRevision;
|
||||||
import io.quarkus.qute.CheckedTemplate;
|
import io.quarkus.qute.CheckedTemplate;
|
||||||
import io.quarkus.qute.TemplateInstance;
|
import io.quarkus.qute.TemplateInstance;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
@ -48,13 +48,14 @@ public class PageResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revisionId == null) {
|
if (revisionId == null) {
|
||||||
if (page.latestRevision.content.startsWith("@")) {
|
PageRevision revision = page.getLatestRevision();
|
||||||
String target = page.latestRevision.content.substring(1);
|
if (revision.getContent().startsWith("@")) {
|
||||||
|
String target = revision.getContent().substring(1);
|
||||||
if (redirectFrom == null)
|
if (redirectFrom == null)
|
||||||
redirectFrom = pageId;
|
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.temporaryRedirect(URI.create("/page/" + redirectService.titleEncoded(target) + "?redirectFrom=" + URLEncoder.encode(redirectFrom, StandardCharsets.UTF_8))).build();
|
||||||
}
|
}
|
||||||
return Response.ok().entity(Templates.page(page, page.latestRevision, false)).build();
|
return Response.ok().entity(Templates.page(page, page.getLatestRevision(), false)).build();
|
||||||
} else {
|
} else {
|
||||||
PageRevision revision = PageRevision.findByIndex(page, revisionId);
|
PageRevision revision = PageRevision.findByIndex(page, revisionId);
|
||||||
if (revision != null) {
|
if (revision != null) {
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package eu.m724.talkpages.page.action;
|
||||||
|
|
||||||
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class AccountService {
|
||||||
|
|
||||||
|
// TODO I think it would be better to accept InetAddress
|
||||||
|
@Transactional
|
||||||
|
public Account addressAccount(String address) {
|
||||||
|
Account account = Account.findById(address);
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
return account;
|
||||||
|
|
||||||
|
account = new Account(address);
|
||||||
|
account.persistAndFlush();
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package eu.m724.talkpages.page.action;
|
package eu.m724.talkpages.page.action;
|
||||||
|
|
||||||
import eu.m724.talkpages.RedirectService;
|
import eu.m724.talkpages.RedirectService;
|
||||||
import eu.m724.talkpages.orm.entity.Account;
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
|
import eu.m724.talkpages.orm.entity.auth.Session;
|
||||||
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
|
@ -18,27 +21,39 @@ import org.jboss.resteasy.reactive.RestResponse;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
@Path("/action")
|
@Path("/action")
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
public class ActionResource {
|
public class ActionResource {
|
||||||
|
@Inject
|
||||||
|
SecurityIdentity identity;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AccountService accountService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ActionService actionService;
|
ActionService actionService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RedirectService redirectService;
|
RedirectService redirectService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HttpServerRequest request;
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/create")
|
@Path("/create")
|
||||||
@Transactional // TODO make this in a service or whatever is it called
|
|
||||||
public Response create(MultivaluedMap<String, String> formData) {
|
public Response create(MultivaluedMap<String, String> formData) {
|
||||||
String title = formData.getFirst("title");
|
String title = formData.getFirst("title");
|
||||||
String content = formData.getFirst("content");
|
String content = formData.getFirst("content");
|
||||||
|
Account account;
|
||||||
|
|
||||||
Account account = new Account("test user #" + new Random().nextInt(10000));
|
if (identity.isAnonymous()) {
|
||||||
account.persistAndFlush();
|
account = accountService.addressAccount(request.remoteAddress().hostAddress());
|
||||||
|
} else {
|
||||||
|
Session session = identity.getAttribute("session");
|
||||||
|
account = session.getAccount();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Page page = actionService.createPage(title, content, account);
|
Page page = actionService.createPage(title, content, account);
|
||||||
|
@ -60,11 +75,18 @@ public class ActionResource {
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/edit")
|
@Path("/edit")
|
||||||
@Transactional // TODO make this in a service or whatever is it called
|
@Transactional // TODO move to service or something
|
||||||
public Response edit(MultivaluedMap<String, String> formData) {
|
public Response edit(MultivaluedMap<String, String> formData) {
|
||||||
String title = formData.getFirst("title");
|
String title = formData.getFirst("title");
|
||||||
String content = formData.getFirst("content");
|
String content = formData.getFirst("content");
|
||||||
Account account = new Account("test user #" + new Random().nextInt(0, 1000));
|
Account account;
|
||||||
|
|
||||||
|
if (identity.isAnonymous()) {
|
||||||
|
account = accountService.addressAccount(request.remoteAddress().hostAddress());
|
||||||
|
} else {
|
||||||
|
Session session = identity.getAttribute("session");
|
||||||
|
account = session.getAccount();
|
||||||
|
}
|
||||||
|
|
||||||
Page page = Page.findByTitle(title);
|
Page page = Page.findByTitle(title);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package eu.m724.talkpages.page.action;
|
package eu.m724.talkpages.page.action;
|
||||||
|
|
||||||
import eu.m724.talkpages.orm.entity.Account;
|
import eu.m724.talkpages.orm.entity.auth.Account;
|
||||||
import eu.m724.talkpages.orm.entity.Page;
|
import eu.m724.talkpages.orm.entity.content.Page;
|
||||||
import eu.m724.talkpages.orm.entity.PageRevision;
|
import eu.m724.talkpages.orm.entity.content.PageRevision;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ import jakarta.transaction.Transactional;
|
||||||
public class ActionService {
|
public class ActionService {
|
||||||
@Transactional
|
@Transactional
|
||||||
Page createPage(String title, String content, Account account) {
|
Page createPage(String title, String content, Account account) {
|
||||||
// title and content is sanitized so only prohibit if necessary
|
//account = Account.findById(account.getName());
|
||||||
|
|
||||||
|
// title and content are sanitized so only prohibit if necessary
|
||||||
if (title.contains("/")) {
|
if (title.contains("/")) {
|
||||||
throw new UnacceptableDataException("Title cannot contain slashes (/). Those are used for sub-pages.");
|
throw new UnacceptableDataException("Title cannot contain slashes (/). Those are used for sub-pages.");
|
||||||
} else if (Page.findByTitle(title) != null) {
|
} else if (Page.findByTitle(title) != null) {
|
||||||
|
@ -18,17 +20,12 @@ public class ActionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Page page = new Page(title);
|
Page page = new Page(title);
|
||||||
page.persist();
|
PageRevision revision = new PageRevision(page, account, content);
|
||||||
|
|
||||||
PageRevision revision = new PageRevision(page);
|
|
||||||
revision.author = account;
|
|
||||||
revision.content = content;
|
|
||||||
revision.delta = content.length();
|
|
||||||
|
|
||||||
page.setLatestRevision(revision);
|
page.setLatestRevision(revision);
|
||||||
account.revisions.add(revision);
|
page.persistAndFlush();
|
||||||
|
|
||||||
revision.persistAndFlush();
|
account.getRevisions().add(revision);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
@ -37,15 +34,12 @@ public class ActionService {
|
||||||
Page editPage(Page page, String newContent, Account account) {
|
Page editPage(Page page, String newContent, Account account) {
|
||||||
// constraints are not checked
|
// constraints are not checked
|
||||||
|
|
||||||
PageRevision revision = new PageRevision(page);
|
PageRevision revision = new PageRevision(page, account, newContent);
|
||||||
revision.author = account;
|
|
||||||
revision.content = newContent;
|
|
||||||
revision.delta = newContent.length() - page.latestRevision.content.length(); // TODO optimize
|
|
||||||
|
|
||||||
page.setLatestRevision(revision);
|
page.setLatestRevision(revision);
|
||||||
account.revisions.add(revision);
|
account.getRevisions().add(revision);
|
||||||
|
|
||||||
revision.persistAndFlush();
|
revision.persist();
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,8 @@ import io.quarkus.qute.TemplateExtension;
|
||||||
import io.quarkus.security.identity.CurrentIdentityAssociation;
|
import io.quarkus.security.identity.CurrentIdentityAssociation;
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@TemplateExtension(namespace = "user")
|
@TemplateExtension(namespace = "user")
|
||||||
public class AuthExtension {
|
public class AuthExtension {
|
||||||
|
|
||||||
public static String name() {
|
public static String name() {
|
||||||
try (InstanceHandle<CurrentIdentityAssociation> handle = Arc.container().instance(CurrentIdentityAssociation.class)) {
|
try (InstanceHandle<CurrentIdentityAssociation> handle = Arc.container().instance(CurrentIdentityAssociation.class)) {
|
||||||
SecurityIdentity identity = handle.get().getIdentity();
|
SecurityIdentity identity = handle.get().getIdentity();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<h1>Creating a page</h1>
|
<h1>Creating a page</h1>
|
||||||
|
|
||||||
<p>Editing as {name}</p>
|
<p>Editing as {user:name}</p>
|
||||||
|
|
||||||
<form action="/action/create" method="post">
|
<form action="/action/create" method="post">
|
||||||
<label for="title">Title</label>
|
<label for="title">Title</label>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<h1>Editing {page.title}</h1>
|
<h1>Editing {page.title}</h1>
|
||||||
|
|
||||||
<p>Editing as {name}</p>
|
<p>Editing as {user:name}</p>
|
||||||
|
|
||||||
<form action="/action/edit" method="post">
|
<form action="/action/edit" method="post">
|
||||||
<input type="hidden" name="title" value="{page.title}">
|
<input type="hidden" name="title" value="{page.title}">
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
<h1>History of {page.title}</h1>
|
<h1>History of {page.getTitle}</h1>
|
||||||
|
|
||||||
{#for revision in revisions}
|
{#for revision in revisions}
|
||||||
{#if page.latestRevision == revision}
|
{#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>
|
<span>
|
||||||
|
<strong>
|
||||||
|
<a href="/page/{page.getTitle}?revision={revision.getIndex}">#{revision.getIndex}</a>
|
||||||
|
({revision.getDelta}) {revision.getTimestamp.toString()} by
|
||||||
|
<a href="/user/{revision.getAuthor.getSlug}">{revision.getAuthor.getName}</a>
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
{#else}
|
{#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.getTitle}?revision={revision.getIndex}">#{revision.getIndex}</a>
|
||||||
|
({revision.getDelta}) {revision.getTimestamp.toString()} by
|
||||||
|
<a href="/user/{revision.getAuthor.getSlug}">{revision.getAuthor.getName}</a>
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<br>
|
<br>
|
||||||
{/for}
|
{/for}
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/page/{page.path}">Back to {page.title}</a></li>
|
<li><a href="/page/{page.getSlug}">Back to {page.getTitle}</a></li>
|
||||||
<li><a href="/page/Talk:{page.path}">Talk:{page.title}</a></li>
|
<li><a href="/page/Talk:{page.getSlug}">Talk:{page.getTitle}</a></li>
|
||||||
<li><a href="/edit/{page.path}">Edit page</a></li>
|
<li><a href="/edit/{page.getSlug}">Edit page</a></li>
|
||||||
</ul>
|
</ul>
|
|
@ -6,7 +6,7 @@
|
||||||
<p>Are you looking for:</p>
|
<p>Are you looking for:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{#for suggestion in suggestions}
|
{#for suggestion in suggestions}
|
||||||
<li><a href="/page/{suggestion.path}">{suggestion.title}</a></li>
|
<li><a href="/page/{suggestion.getSlug}">{suggestion.getTitle}</a></li>
|
||||||
{/for}
|
{/for}
|
||||||
</ul>
|
</ul>
|
||||||
<p>Actions:</p>
|
<p>Actions:</p>
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
{#if old}
|
{#if old}
|
||||||
{#if page.latestRevision != revision}
|
{#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.getIndex} of this page from {revision.getTimestamp.toString()}, authored by {revision.getAuthor.getName}.
|
||||||
<br>
|
<br>
|
||||||
<a href="/page/{page.title}">See current version</a>
|
<a href="/page/{page.getTitle}">See current version</a>
|
||||||
</h3>
|
</h3>
|
||||||
{#else}
|
{#else}
|
||||||
<h4>This is the current revision #{revision.index} authored by {revision.author.name}.</h4>
|
<h4>This is the current revision #{revision.index} authored by {revision.author.name}.</h4>
|
||||||
|
@ -20,5 +20,5 @@
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<small>Modified {revision.timestamp.toString()} | <a href="/history/{page.path}">Full history</a> | <a href="/edit/{page.path}">Edit</a>
|
<small>Modified {revision.timestamp.toString()} | <a href="/history/{page.getSlug}">Full history</a> | <a href="/edit/{page.getSlug}">Edit</a>
|
||||||
{#if !page.title.startsWith("Talk:")} | <a href="/page/Talk:{page.path}">Talk</a>{/if}</small>
|
{#if !page.title.startsWith("Talk:")} | <a href="/page/Talk:{page.getSlug}">Talk</a>{/if}</small>
|
|
@ -1,7 +1,7 @@
|
||||||
<h1>{page.title}</h1>
|
<h1>{page.getTitle}</h1>
|
||||||
|
|
||||||
There is no revision #{revisionId}.
|
There is no revision #{revisionId}.
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/history/{page.path}">View history</a></li>
|
<li><a href="/history/{page.getSlug}">View history</a></li>
|
||||||
<li><a href="/page/{page.path}">Back to {page.title}</a></li>
|
<li><a href="/page/{page.getSlug}">Back to {page.getTitle}</a></li>
|
||||||
</ul>
|
</ul>
|
Loading…
Reference in a new issue